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 

Source: https://habr.com/ru/post/J149454/


All Articles