August 27, 2005
Why encapsulate when you have polymorphism?
I've been wondering how much encapsulation we need when OOP gives us polymorphism. With polymorphism we can make our objects behave like objects from any other class, so why the zeal to hide how methods do their job? If we change implementations we can always write an adapter and remain faithful to our old API.
Time for a conceived example: Imagine a Controller class that sits between your frontend and model. Two methods in the Controller are loadResource() and saveResource(). Internally the Controller delegates calls to both methods to a ResourceManager like this:
public class Controller {
private ResourceManager resourceManager;
public Resource loadResource(String id) {
return resourceManager.load(id);
}
public void saveResource(Resource r) {
resourceManager.save(r);
}
}
My point is that we might as well save our breath and write this:
public class Controller {
private ResourceManager resourceManager;
public ResourceManager getResourceManager()
return resourceManager;
}
}
With this the controller's clients call getResourceManager().load() instead of loadResource(). They know that a ResourceManager is involved. Did we break encapsulation? I don't really think so. Say two years later we need to migrate away from ResourceManager in favor of a similar but incompatible class ResourcePool. With a simple adapter we can make the new ResourcePool act as ResourceManager and keep the old Controller API:
public class Controller {
private ResourcePool resourcePool;
public ResourceManager getResourceManager() {
return new ResourceManager() {
public Resource load(String id) {
return resourcePool.retrieve(id);
}
public Resource save(Resource r) {
resourcePool.store(r);
}
};
}
}
Exposing the ResourceManager has additional advantages: Clients of Controller can pass the ResourceManager on to other objects that need to load and save resources, but don't require other services from Controller. These objects will then only be coupled to the ResourceManager interface, even though it is actually the Controller acting behind the curtains.
Cheating yourself out of API breakage like this will not be applicable or appropriate in all situations. What you should keep in mind is that polymorphism gives you the freedom to not shield everything and still keep your interfaces stable when implementations change.
Comments
Law Of Demeter at the wiki. Lots of discussion, pro and contra.
The bottom line is that there is no bottom line. You can’t advocate either side as The Right Way, though leaning toward compliance will generally help you more than leaning against it.
Personally, I find that if I had to start thinking too deeply about these issues, it’d be time to step back and reconsider the entire class library layout. Bumping up against the Law of Demeter too hard is a pretty good sign that maybe your overall architecture has gotten way too complex for any sane design. When all your classes are half meat, half proxy, something has got to be seriously wrong. (When they are not, well, why would you want to “save yourself the breath” if it’s not you save to begin with?)
This is the corollary of the Law of Demeter.
Posted by Aristotle Pagaltzis (#)
When you're shielding large parts from each other (controller from model, public plugin interface from the rest of the application) you inevitably end up writing interfaces that cover a lot of ground. That's where the Law of Demeter stops working for me because I want to divide these huge interfaces into individual aspects. E.g. the top-level controller becomes a container for sub-controllers each covering closely related use cases. So clients become coupled to a few more classes, but remain dependent on the same number of methods which are now much more structured and easier to navigate. I don't actually feel like I violated the spirit of Demeter when adding structure in this fashion, even if some lines in my code now have two dots in them.
When all your classes are half meat, half proxy, something has got to be seriously wrong.
Can you elaborate?
Posted by
Henning Koch
(#)
Can you elaborate?
By “proxy” I mean methods that do nothing but call down to other objects in the spirit of
return resourceManager.load(id);
whereas “meat” obviously means methods that do in fact implement something.
In other words, if lots of your classes have little other reason to exist than the encapsulation of has-a relationships, it would seem that something has gone very wrong in the basic design of the class library.
What I was getting at is that if you start to run into serious trouble with the Law of Demeter, it is likely so because something is wrong on a design level much further up. One level of method call indirection is not exactly what I would consider serious trouble with the Law of Demeter, however. After all, if it was, container classes would be rather difficult to implement…
And that is all pretty close to what you’re saying.
What I would immediately start getting very uncomfortable about is if two levels of indirection were common – but that's a different issue from yours.
Of course, your initial entry is stated in much broader terms than the explanation in your comment, so my comment is stated in broad terms as well.
Posted by Aristotle Pagaltzis (#)
In other words, if lots of your classes have little other reason to exist than the encapsulation of has-a relationships, it would seem that something has gone very wrong in the basic design of the class library.
Hm, I usually end up with a good number of proxies, mainly for two reasons: As an application grows, I often encounter classes that need a different view on something. In such a case I rather write an adapter before duplicating logic.
The other reason to have proxies is decorators. I regularely use decorators to isolate individual aspects of an implementation. So if I have an interface Foo I make a decorator that adds caching, and another one to chain multiple Foos together. This allows me to concentrate on one issue at a time, and to pick between more flavours of Foo than I have classes.
Another reason for my large number of proxies might be that I'm currently working with Java, and proxies are pretty much free with static typing. They always give me some maintenance scare in Ruby or Perl.
Of course, your initial entry is stated in much broader terms than the explanation in your comment, so my comment is stated in broad terms as well.
Yeah, I guess I could have been clearer. I think my point was that I've been enjoying the possibility of moving smartness out of huge container classes and into the smaller, contained classes, even if it's something as elementary as a list or a map.
Posted by
Henning Koch
(#)
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)