Pluggable Selector

今回はPluggable Selector。
文字から見てplug + ableなので、プラグを差し替えする感覚で振る舞いを変えれる、みたいなイメージだと思う。

本では、

インスタンス固有の振る舞いを簡単にコーディングするにはどうすればいいの

と始まっている。

次のコードのような感じでインスタンス固有の振る舞いを実装しようとすると、
振る舞いの数だけクラスが必要になってしまっている。

class Translator
  def initialize(text_ja: "") 
    @text_ja = text_ja
    translation
  end 

  def say 
    @text
  end 
end

class TranslatorEn < Translator
  def translation
   # 翻訳APIを叩たいて @textに設定する
  end
end

class TranslatorIt < Translator
  def translation
   # 翻訳APIを叩たいて @textに設定する
  end
end

class TranslatorCy < Translator
  def translation
   # 翻訳APIを叩たいて @textに設定する
  end
end

TranslatorEn.new(text_ja: 'こんにちは世界').say   # hello world
TranslatorIt.new(text_ja: 'こんにちは世界').say   # ciao mondo
TranslatorCy.new(text_ja: 'こんにちは世界').say   # helo byd    

解決方法は、メソッド(メッセージ)をインスタンスに保持しよう、とのこと。

class Translator
  def initialize(lang: lang)
    @lang = lang
  end

  def say(obj)
    obj.send(@lang)
  end
end

class Word
  attr_accessor :en, :it, :cy
  def initialize(text_ja: nil)
    @text_ja = text_ja
    translation_from_japanese
  end

  def translation_from_japanese
   # 翻訳APIを叩いて @en、 @it、 @cyに翻訳された値が設定される
  end 
end

ohayo_message = Word.new(text_ja: 'こんにちは世界')

john   = Translator.new(lang: 'en') # 英語
itaria = Translator.new(lang: 'it') # イタリア語
cocoa  = Translator.new(lang: 'cy') # ウェールズ語

john.say(ohayo_message)    # hello world
itaria.say(ohayo_message)   # ciao mondo
cocoa.say(ohayo_message)  # helo byd

このパターンを使うメリットは、サブクラスを作る必要がなくなること。
一方で可読性下がっている感。
いまいち腑に落ちていないけどインスタンス固有の振る舞いを定義したくなったら思い出したい。