The Law of Demeter

Before we delve into the SOLID arena, it's useful to explore a less well-known principle, known as LoD, or the principle of least knowledge. This so-called law has three core ideas:

  • A unit should have only limited knowledge about other units
  • A unit should only talk to its immediate friends
  • A unit should not talk to strangers

You may rightfully wonder what it means for a unit to talk to a stranger. A unit, in this context, is a specific coded abstraction: possibly a function, a module, or a class. And talking here means interfacing with, such as calling the code of another module or having that other module call your code.

This is a very useful and simple law to learn and then apply to all our programming, whether we're writing an individual line of code or designing an entire architecture. It is, however, often forgotten or ignored.

Let's take the example of the simple act of making a purchase in a shop. We can express this interaction with Customer and Shopkeeper abstractions:

class Customer {}
class Shopkeeper {}

Let's also say that the Customer class has a wallet where they store their money:

class Customer {
constructor() {
this.wallet = new CustomerWallet();
}
}

class CustomerWallet {
constructor() {
this.amount = 0;
}
addMoney(deposit) {
this.amount += deposit;
}
takeMoney(debit) {
this.amount -= debit;
}
}

A simplified version of an interaction between the Shopkeeper and the Customer may go something like the following globally:

class Shopkeeper {
processPurchase(product, customer) {
const price = product.price();
customer.wallet.takeMoney(price);
// ...
}
}

This may look okay, but let's consider a real-life analogy of this interaction. The shopkeeper takes the wallet from the customer's pocket and then proceeds to open the wallet and take the desired amount without in any way interacting with the customer directly.

It's immediately obvious that this would never be a socially appropriate interaction in real life, of course, but crucially, the shopkeeper is making assumptions outside of their remit. The customer may wish to pay using a different mechanism, or may not even have a wallet. The nature of the customer's payment is their own business. This is what we mean when we say only talk to friends: you should only interface with abstractions that you should have knowledge of. The shopkeeper here should not (and would not) have knowledge of the customer's wallet and so should not be talking to it.

Taking this learnings on board, we can program a cleaner abstraction as follows:

class Shopkeeper {
processPurchase(product, customer) {
const price = product.price();
customer.requestPayment(price);
// ...
}
}

This now seems more reasonable. The Shopkeeper is talking to the Customer directly. The customer, in turn, will talk to their CustomerWallet instance, retrieving the desired amount and then handing it to the shopkeeper.

We have all likely written code that somewhat violates the LoD. Of course, the code we write is not always as contrived or neatly exemplified by real-life as the interaction between a shopkeeper and a customer, but nonetheless, the LoD still applies. We can illustrate this further with a typical piece of JavaScript that is responsible for displaying a message to the user via the document object model (DOM):

function displayHappyBirthday(name) {
const container = document.createElement('div');
container.className = 'message birthday-message';
container.appendChild(
document.createTextNode(`Happy Birthday ${name}!`)
);
document.body.appendChild(container);
}

This is quite typical and idiomatic frontend JavaScript. To display the Birthday message within a document, we first construct the string ourselves and place it in a text node, which itself is appended to a <div> element with message and birthday-message classes. We then take this DOM tree and append it to the document so it can be viewed by the user.

The DOM is a set of APIs that enables us to interface with a parsed HTML document, usually within the browser. The DOM, as a term, is also used to describe the tree of nodes generated by this parsing process. So, a DOM tree can be derived from a given HTML document, but we can also construct our own DOM trees and manipulate them freely.

Does the preceding code abide by the LoD? Our abstraction here, the displayHappyBirthday function, is concerned with the concept of a happy birthday message and is talking directly to the DOM. The DOM, however, is not its friend. The DOM is an implementation detail—a stranger—in the concept of a Happy Birthday message. The Happy Birthday message mechanism should not be required to have knowledge about the DOM. It would, therefore, be appropriate to build another abstraction that bridges these two strangers:

function displayMessage(message, className) {
const container = document.createElement('div');
container.className = `message ${className}`;
container.appendChild(
document.createTextNode(message)
);
document.body.appendChild(container);
}

Here, we have a more generic displayMessage function that is interfacing directly with the DOM—a friend. Our displayHappyBirthday function could then be changed so that it purely interacts with this displayMessage abstraction:

function displayHappyBirthday(name) {
return displayMessage(
`Happy Birthday ${name}!`,
'birthday-message'
);
}

This code can now be said to be more loosely coupled to the implementation of displayMessage. We could later decide to change the exact mechanism that we use to display messages without altering the displayHappyBirthday function at all. We've therefore bolstered the maintainability of code. By generalizing a common piece of functionality—displaying a message—we also make future features much more seamless—for example, displaying a Happy New Year message:

function displayHappyNewYear(name) {
return displayMessage(
`Happy New Year! ${name}`,
'happy-new-year-message'
);
}

The LoD, at its core, is concerned with which abstractions we feel should interface with other abstractions. It does not provide guidance as to what a friend or a stranger is or what it means for a unit to only have limited knowledge of other units. The law challenges us to define these terms for ourselves, alongside the abstractions we're building. It's our responsibility to stop and consider how our abstractions are interfacing, and whether perhaps we should design them differently.

I chose to write about this principle first as I feel it is the most memorable and most generally useful tool for writing clean code with clean abstractions.

Next, we'll be discussing SOLID and other principles that all, in their own ways, complements the LoD.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.22.136