« The best way to improve team productivity | The Pitfalls of Code Review (And How To Fix Them) » |
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.
Object-oriented programming has a concept known as The Law of Demeter. This rule can be expressed in three key points:
So, what does this mean in practical terms? There are three takeaways from this rule.
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.
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.
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.
There are a couple cases that look like violations of the Law of Demeter, but are in fact not violations of the law.
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.
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.
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 1/13/2015 at 8:32 am
Larry Garfield (@Crell) wrote at 1/13/2015 1:41 pm:
“When that response is an object, that object is a friend.”
So like a good mafioso, one object Witnessed for another? :-)
Mike Lively (@mlively) wrote at 1/14/2015 10:09 pm:
Small clarification on “Methods that return other objects aren’t violations.” If you call a method that returns another object and you then proceed to call methods on that object then you are actually breaking the LoD. You are only supposed to call your own methods, methods of parameters, methods of objects instantiated directly in the function, and methods of class members. You’re right that the return type of the method is part of the contract, the area that it creates a problem is you now need to understand the structure of object A and object B.
Per Persson (@md2perpe) wrote at 1/16/2015 11:16 am:
Doesn’t it make a difference what kind of relation the returned object (R) has to the object (O) on which you called the method?
R can be part of O, like a tail is part of a dog.
Returning parts should be avoided as it’s internal structure.
You shouldn’t do dog.getTail().wag() but dog.wagTail().R can be owner of O, like the owner of a dog.
Returning an owner is imho okay.
It’s better to do dog.getOwner().getName() than dog.getNameOfOwner().
Brandon Savage (@brandonsavage) wrote at 1/17/2015 3:24 pm:
Factories are a perfect example of a case where the returned object is known, expected and using it doesn’t violate The Law of Demeter.
There’s always a fine line here, because it’s easy to teach your object about too many other objects, but that actually violates SRP, not LOD.
« The best way to improve team productivity | The Pitfalls of Code Review (And How To Fix Them) » |