Rails:デメテルの混乱の法則


13

私はRails AntiPatternsという本を読んでいて、彼らはデメテルの法則を破らないために委任を使うことについて話しています。主な例は次のとおりです。

彼らは、コントローラでこのようなものを呼び出すことは悪いと信じています(そして私は同意します)

@street = @invoice.customer.address.street

提案された解決策は、次のことを行うことです。

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

ドットを1つしか使用しないため、ここでデメテルの法則に違反していないと述べています。あなたはまだ顧客を通り抜けて住所を通り、請求書の番地を取得しているので、これは間違っていると思います。私は主に私が読んだブログ投稿からこのアイデアを得ました:

http://www.dan-manges.com/blog/37

ブログの投稿では、主な例は

class Wallet
  attr_accessor :cash
end
class Customer
  has_one :wallet

  # attribute delegation
  def cash
    @wallet.cash
  end
end

class Paperboy
  def collect_money(customer, due_amount)
    if customer.cash < due_ammount
      raise InsufficientFundsError
    else
      customer.cash -= due_amount
      @collected_amount += due_amount
    end
  end
end

ブログ投稿では、のcustomer.cash代わりにドットは1つしかありませんがcustomer.wallet.cash、このコードは依然としてデメテルの法則に違反していると述べています。

Paperboyのcollect_moneyメソッドでは、2つのドットはありません。「customer.cash」に1つのドットがあります。この委任は問題を解決しましたか?どういたしまして。振る舞いを見ると、ペーパーボーイはまだ顧客の財布に直接手を差し伸べて現金を引き出しています。

編集

これはまだ違反でありWallet、支払いを処理するwithdrawというメソッドを作成する必要があり、Customerクラス内でそのメソッドを呼び出す必要があることを完全に理解し、同意します。私が得ていないのは、このプロセスによれば、私の最初の例Invoiceはまだ通りに到達するために直接Customer手を伸ばしているため、デメテルの法則に違反しているということです。

誰かが私が混乱を解消するのを手伝ってもらえますか。私はこのトピックを沈めようとして過去2日間探してきましたが、それでも混乱を招きます。



ブログの2番目の例(paperboy)がデメテルの法則に違反しているとは思わない。それは悪い設計かもしれません(顧客は現金で支払うと仮定しています)が、それはデメテルの法則違反ではありません。すべての設計エラーがこの法律を破ることによって引き起こされるわけではありません。著者はIMOを混同しています。
アンドレスF.

回答:


24

最初の例は、デメテルの法則に違反していませ。はい、現状のコードでは、仮想的なものと@invoice.customer_street同じが得られると言っていますが、トラバースの各ステップで、返される値は質問されているオブジェクトによって決定されます -それは「ペーパーボーイが顧客の財布」、「ペーパーボーイは顧客に現金を要求し、顧客はたまたま財布から現金を取得する」ということです。@invoice.customer.address.street

あなたが言うとき@invoice.customer.address.street、あなたは顧客の知識と内部に対処することを前提としている- これは悪いことです。あなたが言うとき@invoice.customer_street、あなたはinvoice、「ねえ、私は顧客の通りが欲しい、あなたはそれをどうやって手に入れるかを決める」と尋ねいる。その後、顧客は住所に「あなたの街が欲しいのですが、欲しいのですどうやって手に入れるか決めてください」と言います

Demeterの推力は「グラフから遠く離れたオブジェクトのを知ることはできません」ではなく、代わりに「あなた自分値を取得するためにオブジェクトグラフに沿って遠くを横断しないでください

これは微妙な違いのように思えるかもしれませんが、これを考慮してください。Demeter準拠のコードでは、内部表現が変更されたときに、どのくらいのコードを変更する必要がありaddressますか?デメテル非準拠のコードはどうですか?


これはまさに私が探していた種類の説明です!ありがとうございました。
user2158382

非常に良い説明。質問があります:1)請求書オブジェクトが顧客オブジェクトを請求書のクライアントに返したい場合、それは必ずしも内部で保持しているのと同じ顧客オブジェクトであることを意味しません。それは、複数の値を持つ素敵なパッケージ化されたデータセットをクライアントに返すために、その場で作成されたオブジェクトである場合があります。提示するロジックを使用すると、請求書には複数のデータを表すフィールドを含めることはできません。または私は何かが欠けています。
zumalifeguard 14年

2

最初の例と2番目の例は実際にはまったく同じではありません。最初は「1つのドット」の一般的なルールについて話しますが、2番目はオブジェクト指向設計の他のこと、特に「教えて、聞かないで」について話します。

委任は、デメテルの法則違反を回避するための効果的な手法ですが、属性に対してではなく、行動に対してのみです。-2番目の例、ダンのブログから

繰り返しますが、属性のみではなく、動作のみ

属性を要求する場合、要求することになっています。「ねえ、あなたはポケットにどれくらいのお金を持っていますか?見せてください、あなたがこれを支払うことができるかどうかを評価します。」それは間違っています、ショッピング店員はこのように振る舞いません。代わりに、「支払ってください」と言うでしょう。

customer.pay(due_amount)

彼が支払うべきかどうか、そして彼が支払うことができるかどうかを評価することは顧客自身の義務です。そして、店員の仕事は顧客に支払いを命じた後に終了します。

それで、2番目の例は最初の例が間違っていることを証明していますか?

私の考えでは。いいえ、限り:

1.自己制約付きでそれを行います。

@invoice委任によって顧客のすべての属性にアクセスできますが、通常はほとんど必要ありません。

Railsアプリで請求書を表示するページについて考えてください。上部に顧客の詳細を表示するセクションがあります。それでは、請求書テンプレートでは、このようにコーディングしますか?

#customer-info
  = @invoice.customer_name
  = @invoice.customer_address
  ....

それは間違っていて非効率的です。より良いアプローチは

#customer-info
  = render partial: 'invoice_header_customer', 
           locals: {customer: @invoice.customer}

次に、顧客が部分的にすべての属性を処理できるようにします。

したがって、通常は必要ありません。しかし、最近のすべての請求書を表示するリストページがある場合があり、それぞれliに顧客名を表示するブリーフィングフィールドがあります。この場合、顧客の属性を表示する必要があり、テンプレートを次のようにコーディングすることは完全に合法です。

= @invoice.customer_name

2.このメソッド呼び出しに応じて、それ以上のアクションはありません。

上記のリストページの場合、請求書は顧客の名前属性を要求しましたが、実際の目的は「名前を表示してください」であるため、基本的には動作です属性ではありません。この属性に基づく評価やアクションはありません。たとえば、あなたの名前が「マイク」である場合、私はあなたを好きになり、30日間のクレジットを与えます。いいえ、請求書は「あなたの名前を見せて」と言うだけで、それ以上はありません。したがって、例2の「聞かないでください」ルールに従って、これは完全に受け入れられます。


0

2番目の記事でさらに読むと、アイデアがより明確になると思います。アイデアは、顧客に支払い能力を提供し、ケースが保管されている場所を完全に隠す機能を提供するだけです。それはフィールド、ウォレットのメンバー、または他の何かですか?呼び出し側は、その実装の詳細が変更されても、知る必要も、知る必要もなく、変わらない。

class Wallet
  attr_accessor :cash
  def withdraw(amount)
     raise InsufficientFundsError if amount > cash
     cash -= amount
     amount
  end
end
class Customer
  has_one :wallet
  # behavior delegation
  def pay(amount)
    @wallet.withdraw(amount)
  end
end
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.pay(due_amount)
  end
end

ですから、2番目の参考文献は、より役立つ推奨事項を提供していると思います。

「ワンドット」のみのアイデアは、部分的な成功であり、いくつかの深い詳細を隠しますが、別々のコンポーネント間の結合を増やします。


申し訳ありませんが、2番目の例を完全に理解しており、投稿した抽象化を行う必要があることは理解していますが、理解できないのは最初の例です。ブログの投稿によると、私の最初の例は間違っています
-user2158382

0

ダンがこの記事から彼の例を導き出したように聞こえます:The Paperboy、The Wallet、およびThe Law Of Demeter

デメテルの法則オブジェクトのメソッドは、次の種類のオブジェクトのメソッドのみを呼び出す必要があります。

  1. 自体
  2. そのパラメーター
  3. 作成/インスタンス化するオブジェクト
  4. 直接のコンポーネントオブジェクト

デメテルの法則を適用するタイミングと方法

したがって、法律とその利点を十分に理解できましたが、既存のコード内で適用できる場所を特定する方法についてはまだ説明していません(同様に重要なのは、適用しない場所です...)

  1. 連鎖「get」ステートメント -デメテルの法則を適用する最初の、最も明白な場所はget() 、ステートメントが繰り返されるコードの場所です。

    value = object.getX().getY().getTheValue();

    この例の正規の人物が警官に引き渡されたとき、次のように表示されます。

    license = person.getWallet().getDriversLicense();

  2. 多数の「一時的な」オブジェクト -上記のライセンスの例は、コードが次のように見える場合は良くありません。

    Wallet tempWallet = person.getWallet(); license = tempWallet.getDriversLicense();

    それは同等ですが、検出が困難です。

  3. 多くのクラスのインポート -私が取り組んでいるJavaプロジェクトでは、実際に使用するクラスのみをインポートするというルールがあります。あなたは次のようなものを見たことはありません

    import java.awt.*;

    ソースコードで。このルールが適切に設定されている場合、すべて同じパッケージからのインポートステートメントが1ダースほどあることは珍しくありません。これがコード内で発生している場合、曖昧な違反の例を探すのに適した場所です。インポートする必要がある場合は、結合されます。変更された場合、同様に変更する必要があります。クラスを明示的にインポートすることにより、クラスが実際にどのように結合されているかを確認できます。

あなたの例はRubyであることを理解していますが、これはすべてのOOP言語に適用されるはずです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.