10

私はデメテルの法則に従おうとしています ( http://en.wikipedia.org/wiki/Law_of_Demeterhttp://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/を参照) ) 利点はわかりますが、ドメイン オブジェクトに関しては少し行き詰まっています。

ドメイン オブジェクトには当然チェーンがあり、チェーン全体に関する情報を表示する必要がある場合があります。

たとえば、ショッピング バスケット:

各注文には、ユーザー、配送情報、およびアイテムのリストが含まれています。各注文アイテムには、製品と数量が含まれています。各製品には、名前と価格があります。各ユーザーには名前とアドレスが含まれています

注文情報を表示するコードは、注文、ユーザー、製品に関するすべての情報を使用する必要があります。

上記のすべてのオブジェクトに対してクエリを実行し、それらを個別にコードに渡すコードよりも、「order.user.address.city」などの注文オブジェクトを介してこの情報を取得する方が、確かに優れており、再利用可能ですか?

コメント/提案/ヒントは大歓迎です!

4

5 に答える 5

10

などの連鎖参照を使用する際の問題の 1 つorder.user.address.cityは、高次の依存関係がクラス外のコードの構造に「組み込まれる」ことです。

理想的には、クラスをリファクタリングする場合、「強制変更」はリファクタリングされるクラスのメソッドに限定する必要があります。クライアント コードに複数の連鎖参照がある場合、リファクタリングによって、コードの他の場所を変更する必要があります。

例を考えてみましょう。ユーザー、企業、および注文できる電子エージェントをカプセル化した抽象化でUserあるに置き換えたいとします。OrderPlacingPartyこのリファクタリングにより、すぐに複数の問題が発生します。

  • Userプロパティは別の名前になり、異なるタイプになります
  • 新しいプロパティには、注文が電子エージェントによって行われた場合にaddress備えているものがない場合がありますcity
  • 注文に関連付けられた人User(法的な理由でシステムが必要とするものとします) は、間接的に注文に関連付けられている可能性があります。たとえば、OrderPlacingParty.

これらの問題の解決策は、渡されたオブジェクトの構造を「理解」させるのではなく、順序表示ロジックに必要なものすべてを直接渡すことです。このようにして、リファクタリングされるコードへの変更をローカライズできます。潜在的に安定している他のコードに変更を広めることなく。

interface OrderPresenter {
    void present(Order order, User user, Address address);
}
interface Address {
    ...
}
class PhysicalAddress implements Address {
    public String getStreetNumber();
    public String getCity();
    public String getState();
    public String getCountry();
}
class ElectronicAddress implements Address {
    public URL getUrl();
}
interface OrderPlacingParty {
    Address getAddress();
}
interface Order {
    OrderPlacingParty getParty();
}
class User implements OrderPlacingParty {
}
class Company implements OrderPlacingParty {
    public User getResponsibleUser();
}
class ElectronicAgent implements OrderPlacingParty {
    public User getResponsibleUser();
}
于 2012-12-03T18:03:17.187 に答える
2

チェーンを使用してプロパティにアクセスする場合、2 つ (または少なくとも 2 つ) の異なる状況で行われると思います。1 つは、たとえば、プレゼンテーション モジュールで、Orderオブジェクトがあり、所有者/ユーザーの住所や市区町村などの詳細を表示したい場合です。その場合はそのままで問題ないと思います。なんで?アクセスされたプロパティに対してビジネス ロジックを実行していないため、密結合が (潜在的に) 発生する可能性があります。

ただし、アクセスされたプロパティで何らかのロジックを実行する目的でこのようなチェーンを使用する場合は、状況が異なります。たとえば、

String city = order.user.address.city;
...
order.user.address.city = "New York";

これは問題です。なぜなら、このロジックは、ターゲット属性である都市に近いモジュールでより適切に実行される/実行されるべきだからです。同様に、Address オブジェクトが最初に構築される場所、またはそうでない場合は、少なくとも User オブジェクトが構築されるとき (たとえば、User がエンティティで、アドレスが値の型である場合)。しかし、それ以上に進めば進むほど非論理的で問題が大きくなります。ソースとターゲットの間に関与する仲介者が多すぎるためです。

したがって、デメテルの法則によれば、クラスの「都市」属性に対して何らかのロジックを実行している場合、たとえば、 order.user.address.cityのようなチェーンで都市属性にアクセスする OrderAssmebler の場合は、次のように考える必要があります。このロジックをターゲットに近い場所/モジュールに移動します。

于 2012-12-01T17:04:39.297 に答える
1

一般的に言えば、新しい要件やバグ修正がシステム全体に広がらないように、縮小された範囲で変更を維持するのに役立つため、私はデメテルの法則に従います。この方向性に役立つ他の設計ガイドラインがあります。たとえば、この記事にリストされているものです。そうは言っても、デメテルの法則 (およびデザイン パターンやその他の同様のもの) は、トレードオフがあり、そうしても問題ないと判断した場合は、それらを破ることができる有用なデザイン ガイドラインであると考えています。たとえば、主に脆弱なテストを作成するため、私は一般的にプライベートメソッドをテストしません. ただし、いくつかの非常に特殊なケースでは、オブジェクトの実装が変更された場合にその特定のテストが変更されることを知っており、アプリで非常に重要であると考えたため、オブジェクトのプライベート メソッドをテストしました。もちろん、そのような場合は特に注意して、なぜそうしているのかを説明するドキュメントを他の開発者に残す必要があります。しかし、最終的には、適切な判断を下す必要があります:)。

さて、元の質問に戻ります。私が理解している限り、ここでの問題は、メッセージチェーンを介してアクセスできるオブジェクトのグラフのルートであるオブジェクトの (Web?) GUI を作成することです。その場合、モデルの各オブジェクトにビュー コンポーネントを割り当てることで、モデルを作成したのと同様の方法で GUI をモジュール化します。その結果、それぞれのモデルの HTML を作成する方法を知っている 、 などのOrderViewクラスができます。AddressViewその後、これらのビューを構成して最終的なレイアウトを作成できます。ビューに責任を委譲する (例: がOrderViewを作成するAddressView) か、メディエーターを使用します。それらを構成し、モデルにリンクします。最初のアプローチの例として、次のようなものを使用できます (例として PHP を使用します。使用している言語はわかりません)。

class ShoppingBasket
{
  protected $orders;
  protected $id;

  public function getOrders(){...}
  public function getId(){...}
}

class Order
{
  protected $user;

  public function getUser(){...}
}

class User
{
  protected $address;

  public function getAddress(){...}
}

そしてビュー:

class ShoppingBasketView
{
  protected $basket;
  protected $orderViews;

  public function __construct($basket)
  {
     $this->basket = $basket;
     $this->orederViews = array();
     foreach ($basket->getOrders() as $order)
     {
        $this->orederViews[] = new OrderView($order);
     }
  }

  public function render()
  {
     $contents = $this->renderBasketDetails();
     $contents .= $this->renderOrders();     
     return $contents;
  }

  protected function renderBasketDetails()
  {
     //Return the HTML representing the basket details
     return '<H1>Shopping basket (id=' . $this->basket->getId() .')</H1>';
  }

  protected function renderOrders()
  {
     $contents = '<div id="orders">';
     foreach ($this->orderViews as $orderView)
     {
        $contents .= orderViews->render();
     }
     $contents .= '</div>';
     return $contents;
  }
}

class OrderView
{
//The same basic pattern; store your domain model object
//and create the related sub-views

  public function render()
  {
     $contents = $this->renderOrderDetails();
     $contents .= $this->renderSubViews();
     return $contents;
  }

  protected function renderOrderDetails()
  {
     //Return the HTML representing the order details
  }

  protected function renderOrders()
  {
     //Return the HTML representing the subviews by
     //forwarding the render() message
  }
}

そして、view.php で次のようにします。

$basket = //Get the basket based on the session credentials
$view = new ShoppingBasketView($basket);
echo $view->render();

このアプローチは、ビューが構成可能なコンポーネントとして扱われるコンポーネント モデルに基づいています。このスキーマでは、オブジェクトの境界を尊重し、各ビューには単一の責任があります。

編集(OPコメントに基づいて追加)

サブビューでビューを整理する方法はなく、バスケット ID、注文日、およびユーザー名を 1 行で表示する必要があると仮定します。コメントで述べたように、その場合、「悪い」アクセスが単一の十分に文書化された場所で実行され、ビューがこれを認識しないようにします。

class MixedView
{
  protected $basketId;
  protected $orderDate;
  protected $userName;

  public function __construct($basketId, $orderDate, $userName)
  {
    //Set internal state
  }


  public function render()
  {
    return '<H2>' . $this->userName . "'s basket (" . $this->basketId . ")<H2> " .
           '<p>Last order placed on: ' . $this->orderDate. '</p>';
  }
}

class ViewBuilder
{
  protected $basket;

  public function __construct($basket)
  {
    $this->basket = $basket;
  }

  public function getView()
  {
     $basketId = $this->basket->getID();
     $orderDate = $this->basket->getLastOrder()->getDate();
     $userName = $this->basket->getUser()->getName();
     return new MixedView($basketId, $orderDate, $userName);
  }
}

後でドメイン モデルを再配置し、ShoppingBasketクラスがメッセージを実装できなくgetUser()なった場合は、アプリケーション内の 1 つのポイントを変更する必要があります。その変更がシステム全体に広がらないようにしてください。

HTH

于 2012-11-28T20:25:46.083 に答える
1

あなたは正しいです。おそらく、値オブジェクトを次のようにモデル化するでしょう

class Order {
    User user;
}

class User {
    Address shippingAddress;
    Address deliveryAddress;
}

class Address {
    String city;
    ...
}

このデータをデータベース (例: ORM ) に永続化する方法を検討し始めるとき、パフォーマンスについて考え始めますか? 熱心な読み込みと遅延読み込みのトレードオフを考えてください。

于 2012-09-05T15:09:54.143 に答える
0

デメテルの法則は、プロパティ/フィールドへのアクセスではなく、メソッドの呼び出しに関するものです。技術的にはプロパティがメソッドであることは知っていますが、論理的にはデータであることを意図しています。だから、あなたの例はorder.user.address.city私にはうまくいくようです。

この記事は、さらに読むのに興味深いものです: http://haacked.com/archive/2009/07/13/law-of-demeter-dot-counting.aspx

于 2012-12-03T15:25:47.227 に答える