Demeter Law - Data Objects

I am 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 I can see the benefits, but I'm a little stuck when it comes to domain objects.

Domain objects, of course, have a chain, and sometimes it is necessary to display information about the entire chain.

For example, a basket:

Each order contains information about the user, delivery and the list of goods. Each order element contains a product and quantity. Each product has a name and price. Each user contains a name and address

The code displaying order information should use all information about the order, users, and products.

Of course, it’s better and more reusable to get this information through an order object, for example. "order.user.address.city", than for some code above, than making requests for all the objects listed above, then passing them to the code separately?

Any comments / suggestions / tips are welcome!

+8
oop theory law-of-demeter
source share
5 answers

One problem with using chain links, such as order.user.address.city , is that higher-order dependencies are “baked” into the code structure outside the class.

Ideally, when you refactor your class, your “push changes” should be limited to refactoring the class. When there are several chained links in client code, refactoring leads you to changes elsewhere in your code.

Let's OrderPlacingParty at an example: suppose you would like to replace User with OrderPlacingParty , an abstraction encapsulating users, companies, and electronic agents that can place an order. This refactoring immediately presents several problems:

  • The User property will be called something else, and it will have a different type.
  • The new property may not have an address having city in cases where an order is placed by an electronic agent
  • The User person associated with the order (suppose that your system is needed for legal reasons) can be indirectly related to the order, for example, being assigned face to face in the definition of OrderPlacingParty .

The solution to these problems was to convey to the presentation logic everything that it needed directly, and not "understand" the structure of the transferred objects. Thus, you can localize code changes by being refactored without propagating the changes to other code that is potentially stable.

 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(); } 
+7
source share

I think that when a chain is used to access some property, this is done in two (or at least two) different situations. For example, you mentioned, for example, in your presentation module, you have an Order object, and you would like to simply display the owner / user address or data similar to the city. In this case, I think this is not a problem if you do this. What for? Since you are not following business logic on an available property that can (potentially) cause a hard link.

But everything is different if you use such a chain in order to execute some logic on an available property. For example, if you have,

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

This is problematic. Because this logic should / should be properly executed in the module, closer to the target attribute - the city. For example, in the place where the address object is constructed in the first place, or if not, at least when the User object is constructed (if, say, the User is an entity and addresses the value type). But, if he goes further, the further he goes, the further he becomes more illogical and problematic. Because too many intermediaries are connected between the source and the target.

Thus, according to the Law of Demeter, if you follow some logic of the attribute “city” in the class, say OrderAssmebler , which refers to the attribute of the city in a chain similar to the order. user.address.city, then you should consider moving this logic to a place / module closer to the target.

+2
source share

You are right, and you will most likely model value objects like this.

 class Order { User user; } class User { Address shippingAddress; Address deliveryAddress; } class Address { String city; ... } 

When you begin to consider how you will store this data in a database (for example, ORM ), you begin to think about performance. Think impatiently lazy against lazy compromises.

+1
source share

In general, I adhere to the Law of Demeter, since it helps to save changes in a reduced volume, so that a new requirement or error correction does not apply throughout your system. There are other design guidelines that help in this direction, for example. those listed in this article . Having said that, I consider the Law of Demeter (as well as design samples and other similar materials) as useful design guides that have their own compromises, and that you can break them down if you judge that this is normal. For example, I usually test private methods , mainly because it creates fragile tests . However, in some very specific cases, I tested the private method of the object because I considered it to be very important in my application, knowing that this particular test will be subject to change if the implementation of the object changes. Of course, in these cases, you should be more careful and leave additional documentation for other developers, explaining why you are doing this. But in the end, you have to use your good judgment :).

Now back to the original question. As far as I understand, your problem here is to write a GUI (web?) For an object that is the root of the graph of objects that can be accessed through message chains. In this case, I would simulate the graphical interface in the same way as you created your model by assigning a view component to each object of your model. As a result, you will have classes such as OrderView , AddressView , etc. that know how to create HTML for their models. You can then compose these views to create your final layout, either delegating responsibility to them (for example, OrderView creates an AddressView ) or using a Mediator that takes care of their layout and binds them to your model. As an example of the first approach, you can have something like this (I will use PHP as an example, I don’t know which language you use):

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

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 views are considered as composite components. In this scheme, you respect the boundaries of objects, and each view has one responsibility.

Edit (added based on OP comment)

I assume that there is no way to organize the views in subviews and that you need to display the cart id, order date and username on the same line. As I said in the commentary, in this case I would make sure that the “bad” access is performed in one well-documented place, leaving an idea without knowing it.

 class MixedView { protected $basketId; protected $orderDate; protected $userName; public function __construct($basketId, $orderDate, $userName) { //Set internal state } public function render() { return '<H2>' . $this->userName . " 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); } } 

If you later change the model of your domain, and your ShoppingBasket class can no longer implement the getUser() message, then you will have to change one point in your application, preventing this change from spreading throughout your system.

NTN

+1
source share

The law of Demeter is a call of methods, but not access to properties / fields. I know that technical properties are methods, but logically they are for data. So your example order.user.address.city seems fine to me.

This article is interesting for further reading: http://haacked.com/archive/2009/07/13/law-of-demeter-dot-counting.aspx

0
source share

All Articles