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