Law of Demeter - Data objects

前端 未结 5 1275
青春惊慌失措
青春惊慌失措 2021-02-06 01:28

I\'m trying to follow the Law Of Demeter ( see http://en.wikipedia.org/wiki/Law_of_Demeter , http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/ ) as I

5条回答
  •  遇见更好的自我
    2021-02-06 01:51

    Generally speaking I adhere to the Law of Demeter since it helps to keep changes in a reduced scope, so that a new requirement or a bug fix doesn't spread all over your system. There are other design guidelines that help in this direction, e.g. the ones listed in this article. Having said that, I consider the Law of Demeter (as well as Design Patterns and other similar stuff) as helpful design guidelines that have their trade-offs and that you can break them if you judge it is ok to do so. For example I generally don't test private methods, mainly because it creates fragile tests. However, in some very particular cases I did test an object private method because I considered it to be very important in my app, knowing that that particular test will be subject to changes if the implementation of the object changed. Of course in those cases you have to be extra careful and leave more documentation for other developers explaining why you are doing that. But, in the end, you have to use your good judgement :).

    Now, back to the original question. As far as I understand your problem here is writing the (web?) GUI for an object that is the root of a graph of objects that can be accessed through message chains. For that case I would modularize the GUI in a similar way that you created your model, by assigning a view component for each object of your model. As a result you would have classes like OrderView, AddressView, etc that know how to create the HTML for their respective models. You can then compose those views to create your final layout, either by delegating the responsibility to them (e.g. the OrderView creates the AddressView) or by having a Mediator that takes care of composing them and linking them to your model. As an example of the first approach you could have something like this (I'll use PHP for the example, I don't know which language you are using):

    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(){...}
    }
    

    and then the views:

    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 '

    Shopping basket (id=' . $this->basket->getId() .')

    '; } protected function renderOrders() { $contents = '
    '; foreach ($this->orderViews as $orderView) { $contents .= orderViews->render(); } $contents .= '
    '; 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 } }

    and in your view.php you would do something like:

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

    This approach is based on a component model, where the views are treated as composable components. In this schema you respect the object's boundaries and each view has a single responsibility.

    Edit (Added based on the OP comment)

    I'll assume that there is no way of organizing the views in subviews and that you need to render the basket id, order date and user name in a single line. As I said in the comment, for that case I would make sure that the "bad" access is performed in a single, well documented place, leaving the view unaware of this.

    class MixedView
    {
      protected $basketId;
      protected $orderDate;
      protected $userName;
    
      public function __construct($basketId, $orderDate, $userName)
      {
        //Set internal state
      }
    
    
      public function render()
      {
        return '

    ' . $this->userName . "'s basket (" . $this->basketId . ")

    " . '

    Last order placed on: ' . $this->orderDate. '

    '; } } 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); } }

    If later on you rearrange your domain model and your ShoppingBasket class can't implement the getUser() message anymore then you will have to change a single point in your application, avoid having that change spread all over your system.

    HTH

提交回复
热议问题