クラスが大きくなりすぎて
単一の義務の原則に違反し始めた場合、それをさらにいくつかの
関連するクラスに簡単に分割できます。 Rubyが提供する
DelegateClass
構造は、これを支援します。
Person
クラスがあるとしましょう。 システム内のユーザーは、何かを販売したり、記事を公開したりできます。 ここでは、サブクラスは使用できません。ユーザーは同時に作成者と販売者の両方になることができるからです。 リファクタリングしましょう。
クラスは最初は次のようになります。
class Person < ActiveRecord::Base has_many :articles
すべてがよさそうだ。 「Personクラスは、ユーザーが販売したものの数と発行した記事の数を知る必要があります」とあなたは言います。 「ナンセンス」と答えます。
新しい課題がやってくる:ユーザーは売り手/著者と買い手の両方になることができる。 このタスクを完了するには、次のようにクラスを変更する必要があります。
class Person < ActiveRecord::Base
最初に、クラスを変更したため、オープン
性/クローズ
性の
原則に違反しました(拡張性はオープンですが、変更はクローズ)。 第二に、販売/購入を行うとき、クラス名は明らかではありません(「ユーザーがユーザーに販売」、「売り手が購入者に販売」の方が良いでしょう)。 最後に、コード
は共有責任の
原則に違反しています。
今、新しい挑戦が到着したと想像してください。 ユーザーは、リレーショナルではなく、NoSQLデータベースに保存するか、XMLを介してWebサービスから取得する必要があります。
ActiveRecord
の利便性を奪われ、これらの
has_many
はすべて
has_many
なくなりました。 実際、クラスを最初から書き直す必要があり、新しい機能の開発は延期されます。
会う:DelegateClass
Person
クラスを変更する代わりに、デリゲートクラスを使用してその機能を拡張できます。
これらのクラスを使用するには、もう少しコードを書く必要があります。 代わりに
person = Person.find(1) person.items
このコードを使用してください:
person = Person.find(1) seller = Seller.new(person) seller.items seller.first_name
ユーザーをより多くの顧客にすることが簡単になりました。
ActiveRecordからMongoidに切り替える必要がある場合、デリゲートクラスで何も変更する必要はありません。
もちろん、デリゲートクラスは特効薬ではありません。
#reload
ように、一部のメソッドの必ずしも明らかではない動作に慣れるのには時間がかかります。
person = Person.find(1) seller = Seller.new(person) seller.class
別の落とし穴は、デフォルトでは、
#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)
これは
#eql?
という事実に
#eql?
ベースオブジェクト(この場合は
animal
)に対して呼び出されます:
dog.eql?(animal)
一方、
#equal?
デフォルトでは委任されません:
dog.equal?(dog)