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」のスコープにある全域オブジェクトのメソッドは呼び出すことができる
JSで意識すること メモ ES6
JSで意識すること
自分用のJS扱うときに意識することメモ
- 参照のコピーとthis
- list2
- list3
- JSで意識すること
- 参照のコピーとthis
- 見出し1-1
- 見出し1-2
- 見出し1-3
- 非同期Promiseチェーン
- 見出し2-1
- 見出し3
- 見出し3-1
- 参考リンク
- 関連リンク
- 参照のコピーとthis
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に引数渡すと後はなんか知らんけどちゃんとしたドア返ってくるわぐらいの感覚でいられる。