DelegateClassに恋をした

クラスが大きくなりすぎて単一の義務の原則に違反し始めた場合、それをさらにいくつかの関連するクラスに簡単に分割できます。 Rubyが提供するDelegateClass



構造は、これを支援します。



Person



クラスがあるとしましょう。 システム内のユーザーは、何かを販売したり、記事を公開したりできます。 ここでは、サブクラスは使用できません。ユーザーは同時に作成者と販売者の両方になることができるからです。 リファクタリングしましょう。



クラスは最初は次のようになります。

 class Person < ActiveRecord::Base has_many :articles #  has_many :comments, :through => :articles #  has_many :items #  has_many :transactions #  # ? def is_seller? items.present? end #    def amount_owed # =>   end # ? def is_author? articles.present? end #     ? def can_post_article_to_homepage? # =>       end end
      
      





すべてがよさそうだ。 「Personクラスは、ユーザーが販売したものの数と発行した記事の数を知る必要があります」とあなたは言います。 「ナンセンス」と答えます。



新しい課題がやってくる:ユーザーは売り手/著者と買い手の両方になることができる。 このタスクを完了するには、次のようにクラスを変更する必要があります。

 class Person < ActiveRecord::Base # ... has_many :purchased_items #   has_many :purchased_transactions #   # ? def is_buyer? purchased_items.present? end # ... end
      
      





最初に、クラスを変更したため、オープン性/クローズ原則に違反しました(拡張性はオープンですが、変更はクローズ)。 第二に、販売/購入を行うとき、クラス名は明らかではありません(「ユーザーがユーザーに販売」、「売り手が購入者に販売」の方が良いでしょう)。 最後に、コードは共有責任原則に違反しています。



今、新しい挑戦が到着したと想像してください。 ユーザーは、リレーショナルではなく、NoSQLデータベースに保存するか、XMLを介してWebサービスから取得する必要があります。 ActiveRecord



の利便性を奪われ、これらのhas_many



はすべてhas_many



なくなりました。 実際、クラスを最初から書き直す必要があり、新しい機能の開発は延期されます。



会う:DelegateClass


Person



クラスを変更する代わりに、デリゲートクラスを使用してその機能を拡張できます。

 #  class Person < ActiveRecord::Base end #  class Seller < DelegateClass(Person) delegate :id, :to => :__getobj__ #  def items Item.for_seller_id(id) end #  def transactions Transaction.for_seller_id(id) end # ? def is_seller? items.present? end #    def amount_owed # =>   end end #  class Author < DelegateClass(Person) delegate :id, :to => :__getobj__ #  def articles Article.for_author_id(id) end #  def comments Comment.for_author_id(id) end #  def is_author? articles.present? end #     ? def can_post_article_to_homepage? # =>       end end
      
      





これらのクラスを使用するには、もう少しコードを書く必要があります。 代わりに

 person = Person.find(1) person.items
      
      





このコードを使用してください:

 person = Person.find(1) seller = Seller.new(person) seller.items seller.first_name # =>  person.first_name
      
      





ユーザーをより多くの顧客にすることが簡単になりました。

 #  class Buyer < DelegateClass(Person) delegate :id, :to => :__getobj__ #   def purchased_items Item.for_buyer_id(id) end # ? def is_buyer? purchased_items.present? end end
      
      





ActiveRecordからMongoidに切り替える必要がある場合、デリゲートクラスで何も変更する必要はありません。



もちろん、デリゲートクラスは特効薬ではありません。 #reload



ように、一部のメソッドの必ずしも明らかではない動作に慣れるのには時間がかかります。

 person = Person.find(1) seller = Seller.new(person) seller.class # => Seller seller.reload.class # => Person
      
      





別の落とし穴は、デフォルトでは、 #id



メソッドが委任されないことです。 正確にAcitveRecord#id



を取得するには、次の行をAcitveRecord#id



クラスに追加します。

 delegate :id, :to => :__getobj__
      
      





それにもかかわらず、デリゲートクラスはコードの柔軟性を高めるための優れたツールです。




翻訳者から :Sergey Potapovは、 DelegateClass



別の非自明な機能を指摘しています。

 require 'delegate' class Animal end class Dog < DelegateClass(Animal) end animal = Animal.new dog = Dog.new(animal) dog.eql?(dog) # => false, WTF? O_o
      
      





これは#eql?



という事実に#eql?



ベースオブジェクト(この場合はanimal



)に対して呼び出されます:

 dog.eql?(animal) # => true
      
      





一方、 #equal?



デフォルトでは委任されません:

 dog.equal?(dog) # => true dog.equal?(animal) # => false
      
      






All Articles