Good parents teach their children from a young age not to talk to complete strangers, and to tell mom and dad if anyone approaches or tries to talk to them. It makes good sense; children are innocent and will generally believe anything an adult tells them. We do this to protect them.
In object-oriented programming, we have a similar principle for objects. We want to teach our objects not to “talk to strangers”, too. What is talking to strangers for objects? Talking to strangers is relying on objects that we weren’t given, or APIs we haven’t been taught. For example:
<?php $this->someObject ->someOtherObject ->someMethod() ->someReturnedObject ->someOtherMethod() ->someFinalMethod();
Notice how we’re passing through six different objects here, not including the object we’re currently working in. That’s a lot of objects! And that’s a lot of APIs that we don’t know about, could change and break our code, or could have consequences for our functionality.
The Law of Demeter
Object-oriented programming has a concept known as The Law of Demeter. This rule can be expressed in three key points:
- Each object should have limited knowledge of other objects, and only know about objects closely related to itself.
- Each object should only communicate with its “friends”, not strangers.
- Each object should only communicate with its immediate friends.
So, what does this mean in practical terms? There are three takeaways from this rule.
An object should only know about other objects it needs to do its job.
It may seem like prudence to pass an object a laundry list of other objects, “just in case”, but this turns out to be an error. An object should only be told about the other objects it needs to do its job. A good rule of thumb is that an object with more than five constructor arguments probably knows too much or is doing too much.
This is also why object creation becomes a job unto itself for following the Single Responsibility Principle – passing an object a list of other objects in order to create a third object most certainly violates the Law of Demeter, because that third object isn’t a friend. We choose to break that rule for the purposes of object creation, but we do it in a limited way.
Chained calls to methods or properties is a clue we’re breaking the rules.
The code sample at the beginning of the post breaks the Law of Demeter because it chains a bunch of calls together to various objects to accomplish a task. In dealing with such a complex operation, there are other strategies that make far more sense. For example, it would make sense to create some kind of adapter or wrapper for this operation. Perhaps a service is in order here. Or it might make sense to do a refactor, to reduce the number of paths we have to go through to achieve a particular task.
The problem in the opening sample is that we are “reaching through” objects to call other objects, and this means we have to know and respect the API of the objects we’re reaching through. But since we’re not creating a contract between ourselves and that outside object (through type hinting or interfacing), changes to that API could break our code. Not to mention it makes testing the code very difficult.
Injected objects should be finished, not constructed in the current object.
It can be tempting to pass in all the arguments of an object to your object, and then construct the new object when you need it, but this is a violation of the Law of Demeter, too. When you inject objects into another object, it’s assumed that the object needs to know those dependencies; in this case, the object that actually needs to know those dependencies hasn’t been created yet.
This rule doesn’t apply to factories. Factories are a different animal entirely; in fact, their “friends” are the objects that you need to pass in to create the third object. This is perfectly acceptable behavior.
Cases that look like, but aren’t violations.
There are a couple cases that look like violations of the Law of Demeter, but are in fact not violations of the law.
Fluent interfaces don’t violate The Law of Demeter.
A “fluent interface” is an interface that returns $this after the completion of an operation, and allow you to chain calls, like this:
$object->someMethod() ->someOtherMethod() ->someFinalMethod();
This looks like a violation of the Law of Demeter (and almost identical to the opening code sample), but there’s a distinct difference: all of these operations are taking place on a single object. Even if the operation itself calls other objects, the caller doesn’t know or care about that behavior; it’s entirely encapsulated.
Many developers don’t like fluent interfaces for various reasons, including it can be difficult to tell the difference between a fluent interface and a violation of The Law of Demeter. If you like fluent interfaces, feel free to continue using them.
Methods that return other objects aren’t violations.
Some methods may return another object as part or all of the response. This is also not considered a violation of The Law of Demeter. That’s because the “contract” between the object you called includes the response. When that response is an object, that object is a friend.
This can get tricky, though. If you’re making method calls on four or five levels of objects, at some point its worth considering if you’re actually following The Law of Demeter or if you’re “cheating”. That’s a judgement call on your part as the developer.
Frustrated with your company’s development practices?
You don't have to be!
No matter what the issues are, they can be fixed. You can begin to shed light on these issues with my handy checklist.
Plus, I'll help you with strategies to approach the issues at the organization level and "punch above your weight."