【Ruby】クラスとモジュール その2
- 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2013/06/04
- メディア: 単行本
- この商品を含むブログ (22件) を見る
前回 【Ruby】クラスとモジュール - seconの日記 の続き。
クラスを拡張する
既存のクラスにメソッドを追加
既に定義されているクラスにメソッドを追加することも出来る。
# String クラスに文字列中の単語数を数えるメソッド count_word を追加 class String def count_word ary = self.split(/\s+/) # レシーバを空白文字で分解 return ary.size # 分解後の配列の要素数を返す end end str = "Just Another Ruby Newbie" p str.count_word #=> 4
継承
継承により、既存のクラスに変更を加えず新しい機能を追加したり、部分的にカスタマイズして新しいクラスを作ることが出来る。
class RingArray < Array # スーパークラスを指定 def [](i) # 演算子 [] の定義 idx = i % size # 新しいインデックスを求める super(idx) # スーパークラスの同名のメソッドを呼ぶ (この場合 Array#[]) end end wday = RingArray["日", "月", "火", "水", "木", "金", "土"] p wday[6] #=> "土" p wday[11] #=> "木" p wday[-2] #=> "金"
RingArray クラスは、配列サイズより大きなインデックスを指定して参照を行うと、はみ出した部分を先頭からさかのぼってインデックスの計算を行う。
スーパークラスを指定せずに定義した場合、Object クラスの直接のサブクラスとなる。
Object クラスはプログラムを作る際に便利なようにたくさんのメソッドを持っているが、余計なメソッドを排除したい場合は最低限のメソッドしか持たない BasicObject クラスを指定するのがよい。
alias と undef
alias
既に存在するメソッドに別の名前を割り当てる場合に使う。
単にメソッドに別名をつけるだけでなく、既に存在するメソッドの定義を変更する場合に、元のメソッドを別名で呼び出せるように保存しておくために使われる。
class C1 def hello "Hello" end end class C2 < C1 # C1 クラスを継承 alias old_hello hello # 別名 old_hello を設定 def hello # hello を再定義 "#{old_hello}, again" end end obj = C2.new p obj.old_hello #=> "Hello" p obj.hello #=> "Hello, again"
undef
定義されたメソッドをなかったことにしたい場合に使う。
class C3 def hi "Hi" end undef hi end obj = C3.new p obj.hi #=> Error (NoMethodError)
モジュール
モジュールは Ruby の特徴的な機能の1つで、モジュールは処理の部分だけをまとめる機能である。
- モジュールはインスタンスを持つことが出来ない
- モジュールは継承できない
この2つがクラスとモジュールの異なる点である。
モジュールの使い方
モジュールを作る
module HelloModule # モジュールの定義 Version = "1.0" # 定数の定義 def hello(name) puts "Hello, #{name}." end module_function :hello # hello をモジュール関数として公開 end p HelloModule::Version #=> "1.0" HelloModule.hello("Alice") #=> Hello,Alice include HelloModule # モジュールの持つメソッドや定数名を現在の名前空間に取り込む p Version #=> "1.0" hello ("Alice") #=> Hello, Alice
メソッドの定義
モジュール内で定義したメソッドを使うためには、メソッドをモジュール関数として公開する必要がある。
def hello(name) puts "Hello, #{name}." end module_function :hello
また、メソッド内で self (レシーバ)を参照すると、そのモジュールを得られる。
module FooModule def foo p self end module_function :foo end FooModule.foo #=> FooModule
Mix-in
# モジュールをクラスに include module M def meth "meth" end end class C include M end c = C.new p c.meth #=> meth
# include されているかの判定 p C.include?(M) #=> true
クラスCのインスタンスに対してメソッド呼び出しを行うと、クラスC、モジュールM、Object クラス(クラスCのスーパークラス)の順にメソッドを検索し、最初に見つかったものを実行する。
# 継承関係を調べる p C.ancestors #=> [C, M, Object, HelloModule, Kernel, BasicObject] p C.superclass #=> Object
メソッド検索のルール
- 継承の関係と同じように、元のクラスで同じ名前のメソッドが定義されている場合はそちらが優先される。
module M def meth "M#meth" end end class C include M # M をインストール def meth "C#meth" end end c = C.new p c.meth #=> C#meth
- 同じクラスに複数のモジュールを include した場合は、後から include したものが優先される。
module M1 end module M2 end class C include M1 include M2 end p C.ancestors #=> [C, M2, M1, Object, HelloModule, Kernel, BasicObject]
- include が入れ子になった場合も検索順は一列に並ぶ。
module M1 end module M2 end module M3 include M2 end class C include M1 include M3 end p C.ancestors #=> [C, M3, M2, M1, Object, HelloModule, Kernel, BasicObject]
- 同じモジュールを2回以上 include した場合、2回目以降は無視される。
module M1 end module M2 end class C include M1 include M2 include M1 end p C.ancestors #=> [C, M2, M1, Object, HelloModule, Kernel, BasicObject]
extend メソッド
モジュールを特異クラスに include し、オブジェクトにモジュールの機能を追加する。
extend メソッドでは、クラスを超えてオブジェクト単位にモジュールの機能を利用できるようになる。
module Edition def edition(n) "#{self} 第{n}版" end end str = "たのしいRuby" str.extend(Edition) #=> モジュールをオブジェクトに Mix-in する p str.edition(4) #=> "たのしいRuby 第4版"
クラスと Mix-in
Ruby のクラスはそれ自体が Class クラスのオブジェクトとして提供されている。また、クラスメソッドはクラスをレシーバとするメソッドである。つまり、クラスメソッドはクラスオブジェクトに対するインスタンスメソッドである。そのようなメソッドは次の2つである。
- Class クラスのインスタンスメソッド
- クラスオブジェクトの特異メソッド
クラスを継承すると、これらのメソッドはサブクラスにもクラスメソッドとして引き継がれる。
# extend メソッドに酔ってクラスメソッドを追加し、 # include メソッドによってインスタンスメソッドを追加する module ClassMethods # クラスメソッドのためのモジュール def cmethod "class method" end end module InstanceMethods # インスタンスメソッドのためのモジュール def imethod "instance method" end end class MyClass # extend するとクラスメソッドを追加できる extend ClassMethods # include するとインスタンスメソッドを追加できる include InstanceMethods end p MyClass.cmethod #=> "class method" p MyClass.new.imethod #=> "instance method"
オブジェクト指向プログラミングの特徴
カプセル化
オブジェクトが管理するデータをオブジェクトの外部から直には操作できないようにし、変更したり参照したりするときは必ずメソッドを呼び出させるようにすること。
カプセル化のメリットは主に2つあり、1つは不整合なデータをオブジェクトに設定してプログラムの挙動がおかしくなるといったことを防げるようになることである。Ruby ではもともとカプセル化が強制されていて(オブジェクト外部からインスタンス変数に直にアクセス出来ない)、 attr_accessor などのアクセスメソッドはむやみに使用せず必要な物だけを公開するべきである。
もう1つは、具体的なデータや処理をオブジェクトの内部に隠蔽して抽象的に表現できることだ。オブジェクトの内部で保持する具体的なデーが構造が変更されても、外部から見えるメソッドの名前や機能に変化がなければクラスの利用者は内部の変化を気にせず使うことが出来る。クラスを作成する側も、適切なメソッドを用意しておけばクラスの利用側のことを気にせず内部を変更できる。
ポリモルフィズム
オブジェクト指向の用語で、1つのメソッド名が複数のオブジェクトに属することをいう。多相性、多様性ともいう。
# to_s メソッドの例 obj = Object.new # Object str = "Ruby" # String num = Math::PI # Float p obj.to_s #=> "#<Object:0x00000002f4ad38>" p str.to_s #=> "Ruby" p num.to_s #=> "3.141592653589793"
いずれも to_s メソッドを使っているが、実際の文字列を作る手順はオブジェクトが表現するデータによって異なっている。String クラスも Float クラスも Object クラスから派生しているが、 Object クラスから継承した to_s メソッドを定義しなおして、よりふさわしい文字列を返すバージョンの to_s メソッドを提供している。
ダックタイピング
ポリモルフィズムを積極的に活用した考え方。
オブジェクトを特徴づけるのは実際の種類(クラスとその継承関係)ではなく、そのオブジェクトがどのように振る舞うか(どんなメソッドを持っているか)である。
「アヒルのように歩きアヒルのように鳴くものはアヒルに違いない」という格言から来ている。
# 文字列を含む配列から文字列を取り出して、 #その要素に含まれるアルファベットを小文字にして返す def fetch_and_downcase(ary, index) if str = ary[index] return str.downcase end end ary = ["Boo", "Foo", "Woo"] p fetch_and_downcase(ary,1) #=> "foo"
この fetch_and_downcase メソッドが、引数として渡されるオブジェクトに期待しているのは、
- ary[index]という形式で要素が取り出せる
- 取り出した要素が downcase メソッドを持っていること
である。この条件を満たしていればよいので、配列ではなくハッシュを渡して使うことも出来る。
hash = {0 => "Boo", 1 => "Foo", 2 => "Woo"} p fetch_and_downcase(hash,1) #=> "foo"
「同じ操作を行えるならば実際は違うものであってもその違いを気にしない」、「実際は違うものであっても同じ名前のメソッドを用意することで処理を共通化できる」というのがダッチタイピングの考え方である。
モジュールとクラスについては、プログラミングに慣れてからもう一度復習したほうが良さそう。