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」のスコープにある全域オブジェクトのメソッドは呼び出すことができる

rubyでデザインパターン strategyパターン

strategy(戦略)パターン
戦略を切り替えるパターンです。
処理を他のクラスに委譲することを前提とし、クライアント(この例ではsorterです。)が自由にそのクラスを切り替えることが出来ます。

strategyとtemplate methodパターンの違いはこんな説明がわかりやすかったです。
Strategyの方は,全く異なるアルゴリズムごっそり差し替えるためのパターン。
一方でTemplate Methodの方は"template method"の名の通り,アルゴリズムの実行ステップを手順として規定しておいて,各ステップ内の処理をサブクラスに実装させている. Template methodは将来の実装のためのガイドとも言える。 なお実行ステップを記述するtemplate methodは当然overrideされないようにしておく.
また、継承使ったらtemplate_methodで委譲したらstrategyパターンみたいな解説もあるのでちょっと曖昧。

class SortStrategy
    def sort(dataset)
    end
end

class BubbleSortStrategy < SortStrategy
    def sort(dataset)
        puts "バブルソートを実行"
        dataset
    end
end

class QuickSortStrategy < SortStrategy
    def sort(dataset)
        puts "クイックソートを実行"
        dataset
    end
end

class Sorter
    attr_reader :sorter
    
    def initialize(sorter)
        @sorter = sorter
    end
    
    def sort(dataset)
        @sorter.sort(dataset)
    end
    
end

dataset = [1,4,5,6,6,7]
sorter = Sorter.new(BubbleSortStrategy.new)
sorter.sort(dataset)
sorter = Sorter.new(QuickSortStrategy.new)
sorter.sort(dataset)

rubyでデザインパターン builderパターン

砂糖水を作る(
ビルダ (Builder) | Ruby デザインパターン | 酒と涙とRubyとRailsと
)
こちらの写生です。

操作と手順を分離するパターンです。
factoryパターンとの大きな違いは、factoryパターンは作成が1ステップでできる場合に使うのに対し、builderパターンは作成に複数のステップを踏む場合に使う点。

class SugarWater
    attr_accessor :water, :sugar
    
    def initialize(water, sugar)
        @water = water
        @sugar = sugar
    end
    
end

class SugarWaterBuilder
    def initialize
        @sugar_water = SugarWater.new(0,0)
    end
    
    def add_sugar(sugar_amount)
        @sugar_water.sugar += sugar_amount
    end
    
    def add_water(water_amount)
        @sugar_water.water += water_amount
    end
    
    def result
        @sugar_water
    end
end

class Director
    def initialize(builder)
        @builder = builder
    end
    
    def cook
        @builder.add_water(150)
        @builder.add_sugar(90)
        @builder.add_water(300)
        @builder.add_sugar(35)
    end
end

builder = SugarWaterBuilder.new
director = Director.new(builder)
director.cook
p builder.result






####################別の例#####################
class Burger
    attr_reader :size, :cheese, :lettuce, :tomato, :pepperoni
    
    def initialize(builder)
        @size = builder.size
        @cheese = builder.cheese
        @lettuce = builder.lettuce
        @tomato = builder.tomato
    end
end

class BurgerBuilder
    attr_reader :size, :cheese, :lettuce, :tomato, :pepperoni
    
    def initialize(size)
        @size = size
    end
    
    def addPepperoni
        @pepperoni = true
    end
    
    def addLettuce
        @lettuce = true
    end
    
    def addCheese
        @cheese = true
    end
    
    def addTomato
        @tomato = true
    end
    
    def build
        Burger.new(self)
    end
end

burgar = BurgerBuilder.new(14)
burgar.addTomato
burgar.addCheese
tomato_cheese_burgar = burgar.build
p tomato_cheese_burgar

rubyでデザインパターン abstract_factoryパターン

ドアを作るには専門のドア職人にお願いしなければなりません。
しかしドア職人も万能ではありません。木製のドアしか作れない職人、鉄製のドアしか作れない職人と専門があります。
この際のオブジェクトをグループ化したいです。つまり、木製のドアは木製のドアしか作れない職人と一緒にする。鉄製のドアは鉄製が得意な職人と一緒にするといった感じです。ここで心配なことがあります。木製のドアに鉄製のドアが専門の職人を一緒にするといったことがありえそうです。
これを解決するのがabstract_factoryパターンです。複数のオブジェクトをまとめて作るファクトリーといった感じです。

class Door
 def getDescription
 end
end

class WoodenDoor < Door
    def getDescription
        puts "木のドアです。"
    end
    
end

class IronDoor < Door
    def getDescription
        puts "私は鉄のドアです。"
    end
end

class DoorFittingExpert
    def getDescription
    end
end

class Welder < DoorFittingExpert
    def getDescription
        puts "私が取り付けられるのは木製のドアだけです。"
    end
end

class Carpenter < DoorFittingExpert
    def getDescription
        puts "私が取り付けられるのは鉄製のドアだけです。"
    end
end

class DoorFactory
    def makeDoor
    end
    
    def makeFittingExpert
    end
end

class WoodenDoorFactory < DoorFactory
    def makeDoor
        WoodenDoor.new
    end
    
    def makeFittingExpert
        Welder.new
    end
end

class IronDoorFactory < DoorFactory
    def makeDoor
        IronDoor.new
    end
    
    def makeFittingExpert
        Carpenter.new
    end
end

wooden_door_factory = WoodenDoorFactory.new
door = wooden_door_factory.makeDoor
expert = wooden_door_factory.makeFittingExpert
door.getDescription
expert.getDescription

iron_door_factory = IronDoorFactory.new
door = iron_door_factory.makeDoor
expert = iron_door_factory.makeFittingExpert
door.getDescription
expert.getDescription

rubyでデザインパターン factory_methodパターン

面接官が面接を行います。しかし、一人でマーケターやエンジニアなどすべて職種の面接官を一人で行うことは出来ません。エンジニアの面接はエンジニアにお願いし、マーケターの面接はマーケターにしてもらいましょう。
ここでのfactoryはHiringManagerです。このクラスのサブクラスであるDevelopmentManagerとMarketingManagerでmakeInterviewerというメソッドを用意しクラス生成を行なっている。このメソッドのことをファクトリーメソッドという。
テンプレートメソッドパターンをどのオブジェクトを生成するかに使ったパターンです。factory_methodパターンはtemplate_methodパターンの一種と言える。

class Interviewer
    def askQuestion
    end
end

class Developer < Interviewer
    def askQuestion
        puts "開発について"
    end
    
end

class CommunityExecutive < Interviewer
    def askQuestion
        puts "コミュニティについて"
    end
    
end


class HiringManager
    def makeInterviewer
        
    end
    
    def takeInterview
        interviewer = makeInterviewer
        interviewer.askQuestion
    end
end

class DevelopmentManager < HiringManager
    def makeInterviewer
        Developer.new
    end
end

class MarketingManager < HiringManager
    def makeInterviewer
        CommunityExecutive.new
    end
    
end
    
marketer = MarketingManager.new
developer = DevelopmentManager.new

marketer.takeInterview
developer.takeInterview

rubyでデザインパターン simple_factoryパターン

module Door
	
	def getWidth
	end

  def getHeight
  end
end

class WoodenDoor
  include Door
  attr_reader :width, :height

  def initialize(width, height)
    @width = width
    @height = height
  end

  def name
    "木製のドアです。"
  end

end

class DoorFactory

  def self.makeDoor(width, height)
    WoodenDoor.new(width, height)
  end
end

door = DoorFactory::makeDoor(100, 200)
puts door.width
puts door.height
puts door.name

simple factoryパターンは、クライアントが必要とするインスタンスを生成するもの。このとき、インスタンス生成ロジックはクライアントから見えないようにうまく隠しておきます。
WoodernDoorを生成する前に何かロジックを加える必要がある場合はこのようにファクトリーで隠蔽するのが良い。コードを書く人はDoorFactoryに引数渡すと後はなんか知らんけどちゃんとしたドア返ってくるわぐらいの感覚でいられる。