How we are supposed to use OOP

Or what is the Law of Demeter?

Oleksandr Redka
C# Programming

--

The Meme

A few days back, I saw a meme related to real-life reflection in the form of source code. One thing immediately caught my eye — The Law of Demeter is violated in this snippet. Wanna know more? Let's dive into this example.

The meme itself

Domain definition

The first and most important thing would be to define our domain. From this example, we can see a few key things that will be important later:

  • We are modeling some real-life processes that reflect people and their interactions.
  • Each person has hobbies (piano), memberships (gym), a job, assets (car, watch), a home, tattoos, a hairstyle, friends, etc.
  • Certain life events (midlife crises) can trigger changes in a person.

Existing code analysis

Now, let's analyze the given snippet line by line and see what can be wrong there. I will not focus on minor details, such as naming, code style, etc. Also, I will not take into account pseudo code like NormallStuff.

The main goal of this analysis is to see how the 'carl' entity is used and how this usage could evolve.

Age Calculation

Firstly, we see that age is calculated based on the date of birth. Given our domain, we might expect age to be used in different scenarios, such as other age-related events like retirement. But with the given approach, we must always calculate age manually, leading to accumulated code duplications.

Exposed collection properties

Collections, such as Memberships, Assets, Tattoos, and Friends, can be modified by any owner of the 'carl' instance. In practice, we can't enforce any domain rules over those collection modifications. Or, if we would try to implement them, we would have to either create a separate domain service or copy-paste domain rules to all places with 'carl' entity usage.

For example:

  • If 'carl' has any preferences for his memberships, we would have to enforce those preferences directly in the loop.
Carl does not want to visit a boring gym.
  • When 'carl' exposes his assets collection to the general public, anyone can erase assets collection the same way friends are cleared.
Clearing Carl's assets against his will

I guess now you can imagine the magnitude of problems we could get by exposing mutable collections. So, let's move to the next issue.

Exposed properties with rich behavior

By exposing property' Home,' we again allow unauthorized access to protected behaviors. For example, we can create ridiculous surprises like this:

I call this "Surprise Demolition."

Analysis Summary

With existed codebase, our entities are protected only by a thin level of trust to codebase owners. There are no effective ways to define domain rules and ensure all entities' clients are exposed to them. Coincidently, some of the problems described above can be solved by following The Law of Demeter.

Principles overview

The Law Of Demeter

The main idea of this principle is to limit coupling between objects by limiting their dependency on closest friends.

For example, a car consists of many components connected to each other. Humans drive a vehicle by using a simplified interface, allowing the car to solve all other driving-related challenges. When you want to accelerate, you are adding extra pressure on the acceleration pedal, but you are not controlling fuel-air mixture richness yourself.

In other words, it's preferable to interact with other objects using a simplified interface and not manual interaction with internal object components.

We are interacting with Assets' owner and not with Assets' collection

Here, we call Carl's method to add an asset to his collection, which would effectively hide all other options from us.

Tell Don't Ask

Another great principle that correlates with The Law Of Demeter is TellDontAsk. In general, this means we need to tell objects what they need to do instead of requesting objects' data and changing it down the road.

Tell-Don’t-Ask is a principle that helps people remember that object-orientation is about bundling data with the functions that operate on that data. It reminds us that rather than asking an object for data and acting on that data, we should instead tell an object what to do. This encourages to move behavior into an object to go with the data.
- Martin Fowler

For our case, we would tell Carl to renovate his Home instead of invoking renovation ourselves.

Before/After comparison

Now we can run all business rules in the RenovateHome method before jumping to renovation.

Let's make it better.

I will review two options for managing Carl's transition to a midlife crisis. The first option will only fix minor problems and keep the existing while-loop. The second would be more sophisticated and give Carl much more behavior than he currently has.

Option 1: we are controlling Carl's preferences

Let's replace property calls with specialized methods and see how they will change our loop.

Interaction by a strictly defined interface

First of all, we can appreciate the fact that now it's not possible to manipulate Carl's assets without his will, as the assets collection is not exposed.

More than that, we can enforce additional business rules inside Carl's class:

Additional business rules implementation

This change on its own will already reduce a lot of code duplications related to enforcing business rules. However, we can go even further and limit our interaction with Carl to a single operation.

Option 2: we are allowing Carl to decide his fate (preferable)

This option will encapsulate the logic relevant to Carl's case. We will only instruct Carl to enter a midlife crisis when applicable.

Simplified loop

Now, it's up to Carl's internal logic to decide whether it's possible to have a crisis now or not. More than that, now we need only two properties and one method of carl, which will drastically reduce possible misuse and reinforce your SOLID principles. Let's take a look at the EnterMidlifeCrisis method itself.

Now, this method is a part of Carl's class.

Firstly, we can no longer execute all these actions at a random point in time. This method will ensure that right now entity is in the proper state for a transition. Also, Carl's class has complete control over this behavior, meaning that all business rules related to Carl's preferences are already built-in, so we don't need to double-check them.

Conclusion

We've just finalized a code review for a meme. What a time to be alive, huh?

Shortly, don't hesitate to connect your data and behavior. You will easily avoid things like code duplications or missed business rules. More than that, when your entities allow uncontrolled state changes, you have an infinite opportunity for strange bugs. These opportunities will be happily (and unintentionally) exploited by engineers from your team.

I hope this article gave you an alternative view of your current problems. Feel free to tell your story here, and let's see what we can do.

Check out my other articles.

Are you confused with Dispose Pattern? Here is a 5-minute explanation for the most common use cases.

Wanna know how much LINQ to objects is slower than conventional for-loop? I've measured that for you.

References

All links listed here are non-affiliate.

The screenshots above are made from the source code that can be found here:

Useful articles:

Fancy Syntax Highlight provided by ReSharper.

--

--