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