Ruby オブジェクト指向 メモ

オブジェクト指向で意識すること

  • インスタンス変数はアクセサメソッドで提供するようにする。
  • クラスの中で他のクラスをnewするのは出来るだけ避ける。
  • 依存関係の逆転

インスタンス変数はアクセサメソッドで提供するようにする。

class Human
    def initialize(name)
        @name = name
    end
    
    def name
        @name
    end
end

hoge = Human.new('tarou')

puts hoge.name

上のようにアクセサで提供する。いろんな場所でhuman.nameと使われているときに例えばnameメソッドだけ変更 すればよくて済む。rubyならattr_reader使えばよい。

クラスの中で他のクラスをnewするのは出来るだけ避ける。

あるオブジェクトの中でnewするとそのクラスと強い結合が生まれてしまう。 出来ることなら依存オブジェクトの注入(DI)をする。これをすることで注入されたオブジェクトが何であるかには依存しなくなり、注入されたオブジェクトのインターフェースにのみ依存するようになる。ただ、色々理由でDI出来ないこともよくある。その場合は以下のように出来るだけ隔離し依存を明らかにしておく。

class Gear
    def initialize(chainring, cog, rim, tire)
        @chainring = chainring
        @cog = cog
        @rim = rim
        @tire = tire
    end

    def gear_inches
        ratio*wheel.diameter
    end

    def wheel
        @wheel ||= Wheel.new(rim, tire)
    end
end

ここからさらにgear_inchesメソッドが複雑化した場合は

def gear_inches
        #ここがかなり複雑化する
        ratio*diameter
end

def diameter
      @wheel.diameter
 end

このように外部メッセージを隔離し、依存を明らかにしておく。 この変更により外部メッセージである@wheel.diameterに依存していたgear_inchesは 自身のメソッドであるdiameterに依存するようになり、外部への依存を取り除けた。 仮にwheelクラスがdiameterメソッドの名前を変更したりした場合でも副作用はこのシンプルな ラッパーメソッドだけで済む。この副作用を駆逐するために最初から問題を避ける依存関係逆転というテクがある。

依存関係の逆転

class Gear
    attr_reader :chainring, :cog
    def initialize(chainring, cog)
        @chainring = chainring
        @cog = cog
    end

    def gear_inches(diameter)
        ratio*diameter
    end

    def ratio
        chainring / cog.to_f
    end

end

class Weel
    attr_reader :rim, :tire, :gear
    def initialize(rim, tire, chainring, cog)
        @rim = rim
        @tire = tire
        @gear = Gear.new(chainring, cog)
    end

    def diameter
        rim + (tire*2)
    end

    def gear_inches
        gear.gear_inches(diameter)
    end
end

このように依存関係を逆転させる際はより安定したクラスに依存するようにする。 抽象的なクラスまたは、他とあまり結合していないクラスに依存させる。

デメテルの法則

必ずしも守る必要はない。 クラス間の結合を減らすテクニック。 たくさんドットでつなげるとその通り道のクラス全てと結合して再利用が難しいクラスになってしまう。

class LawOfDemeterExample
    def initialize
        @another_class = AnotherClass.new
    end
    #自身が直接アクセスできるメソッドはOK
    def sample_method_following_demeter(obj)
        my_method#オブジェクトO自身のメソッドは呼び出すことができる
        data1 = @another_class.get_data#呼び出し用のメソッドまたは属性として同じクラス内で宣言されたオブジェクトのメソッドは呼び出すことができる
        data2 = obj.get_data#メソッド「sample_method_following_demeter」の引数のメソッドは呼び出すことができる
    end

    def my_method
    end
end

class AnotherClass
    def get_data
    end
end

O = LawOfDemeterExample.new

メソッドsample_method_following_demeterをもつクラスLawOfDemeterExampleのインスタンスをOとすると オブジェクト指向プログラミングにデメテルの法則を適用すると、次のように解釈できる。

  • オブジェクトO自身のメソッドは呼び出すことができる
  • メソッド「sample_method_following_demeter」の引数のメソッドは呼び出すことができる
  • メソッド「sample_method_following_demeter」の内部で生成/初期化したオブジェクトのメソッドは呼び出すことができる
  • 呼び出し用のメソッドまたは属性として同じクラス内で宣言されたオブジェクトのメソッドは呼び出すことができる
  • オブジェクトOがアクセスできるメソッド「sample_method_following_demeter」のスコープにある全域オブジェクトのメソッドは呼び出すことができる