April 23rd, 2006
If you only take away one thing from this series, take this:
DO NOT design for reuse. Design for loose coupling, high cohesion. Do that and reuse will happen on its own.
I have seen many projects go stray because the developers were too focused on designing reusable objects. They ended up creating overly complicated class hierarchies that very quickly became hard to maintain. Why? Because the developers did not focus on understanding the levels of coupling and cohesion in their systems. So, to avoid the tragedies of the past, I figured it would be good, in this article, to give you my interpretation of what coupling is, and what cohesion is. The next set of articles I will go over how to measure your coupling and cohesion, and then based measure, how to create a refactoring plan to improve your designs.
- OOAD 101 Lesson 1: What is an object and what is object-oriented?
- OOAD 101 Lesson 2: Coupling and cohesion trump reuse and inheritance
- OOAD 101 Lesson 3: Understanding Coupling
- OOAD 101 Lesson 4: Understanding cohesion
Coupling describes how dependant one object is on another object (that it uses). Objects that are loosely coupled can be changed quite radically without impacting each other. The slightest change to objects that are tightly coupled can cause a host of problems.
Cohesion tells us how narrowly defined an object is. An object with high cohesion is defined for one purpose and it only performs that purpose. An object with low cohesion tends to try to do a lot of different things. For example, if our Card object was responsible for drawing itself, sending messages back to the server, and executing game logic, it would have low cohesion because that one class is attempting to do too much.
Cohesion also tells us if too many objects are attempting to do the same thing. So, if I have a system of objects to implement my NetRunner game and every card has a unique class with a rendering method and that method is nearly identical in all classes, then that system has low cohesion because too many objects are doing the same thing.
Object coupling is well understood by most programmers and instructors tend to do a good job describing it. Conceptually, it is fairly easy to grasp. Object cohesion, on the other hand, is very poorly understood by the industry. While most professionals will have very good and concrete advice to give you regarding coupling, when it comes to cohesion all they tell you is that you want to design to “high” cohesion. However, they can not tell you how to measure cohesion nor can they tell you how to increase it or how to identify cohesion problems.
Now here is a startling thing that I have found out in the course of my career:
inheritance tightens coupling and lowers cohesion.
That’s right, inheritance, one of the core constructs of object-oriented systems has a negative impact on your designs. There is a place to do inheritance, but consider it a dangerous design operation and be sure it is necessary.
As I said in the last article, OOAD is the act of assigning data and behavior to a system of objects. When I need to group a set of behaviors to one object, I am defining an object with aggregate behavior. The common design flaw I see is when a developer uses inheritance to achieve aggregate behavior. When you do this, you are definetly hurting cohesion, because the ultimate object that will get instantiated has methods for all that behavior you aggregated. The better approach is to use object composition. With object composition you have a set of objects, each one focusing on a single behavior that aggregate to a parent object. Ok, I am getting a bit abstract now and may have lost you (it was your snore that gave it away).
Lets get concreate. For example, to implement all the cards we have in NetRunner, I could take the approach where I create a CCGCard base class, then a subclass for each collectible card game game, and then a sub-class for each card type in the NetRunner game. The problem is that every time I add a card, I would need to add a new class to support that card. NetRunner has nearly 600 cards and BattleTech (another collectible card game I might want to implement) nearly 900. That would mean we would have to create nearly 1500 classes to support all those cards. What’s worse is that all the BattleTech cards are tightly coupled to all the NetRunner cards via the common CCGCard class. If just one NetRunner card requires us to make a change in CCGCard, we could potentially break all the BattleTech cards.
So, instead, use object composition to define cards. A card is simply a generic collection of properties and functions. What’s that you say? Well, I could have a Card class with a name field and a rotate method. That would be the inheritance approach. Alternatively, I could have a Card class that had a hash of fields and one generic getProperty(String name) and setProperty(String name, Object value). This object could support any number of fields. For behaviors it could have a hash of function objects that implemented a consist interface. Say:
void execute(Card card, Request req)
where request would contain parameters in a named value pair list.
In this system, we would still have a lot of classes, but most of those classes would now be function objects. And these objects would have good cohesion and be loosely coupled and therefore much easier to maintain.
Entry Filed under: Software Development