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