OOAD

Object-Oriented Design: Applying Cohesion and Coupling Principles to Create Well-Formed Objects

posted in: OOAD, Software Engineering | 0

Cohesion and coupling are architectural concerns in the design of well-formed objects. Well-formed objects are cohesive and loosely coupled. Poor cohesion and tight coupling are design flaws, and are reasons to refactor a class architecture. (For an overview of object-oriented design, have a look at Object-Oriented Design Basics: Objects, Classes and Inheritance.)

Cohesion

Cohesive objects do one thing, and avoid doing other things.

The main indicator of a lack of cohesion is excessive decision logic. If an object has to put too much work into deciding what it is going to do when it’s in use, then the object is addressing too many unrelated concerns.

Example of Lack of Cohesion

Here’s an example (in JavaScript) of an object that lacks sufficient cohesion:

The architectural idea here is that because all shapes have an area, we should have a single area method to support all of them. On the face of it, that makes sense. But the trouble is that different shapes calculate the area in different ways. We have to add decision logic to determine what kind of shape we are working with, and we have to go through that decision logic every time we invoke the area method. Also, the more shapes we add, the heavier the decision logic becomes. So, we have an object with excessive decision logic, an object that spends too much time deciding what it’s going to do when it’s in use.

Cohesion problems often arise from situations like this, where certain related objects do the same thing but do it differently. So, when applying the principle of cohesion, it is important to keep in mind not only what the object does, but also how it does it. If different instances of an object do the same thing differently, it’s better to break the design into different classes.

How to Fix It

In the case of our Shape class, we’re better off breaking the class into separate classes, and implementing area in each of them in a manner appropriate to the particular shape:

With this change, there is no longer a need to evaluate which type of shape we are dealing with when calculating an area. Consumers of the objects only need to instantiate the type of object they want, rather than telling the Shape object what type of object they want when they instantiate it. So, although we now have three classes instead of one, the classes are much simplified and are easier to use as compared to the first design.

Note that a square is a rectangle, so our Square class can inherit from Rectangle. It also makes sense to have it do so, because they both the same formula to calculate area. A square differs from a rectangle only in that all sides are equal instead of just opposite sides as with a rectangle. So our Square object just needs to ensure that the values passed to our Rectangle constructor’s x and yare the same. To do this, we can pass the length of one side to the Square constructor, and send it to both x and y in the Rectangle constructor.

Coupling

Objects with loose coupling minimize the amount of communication that they have to have with other objects to do what they do.

Objects that are too tightly coupled know more than they need to about each other, and have to spend unnecessary time interacting with each other to do the work they are intended to do. Also, when objects are too tightly coupled, it is difficult to change one without changing the other.

Example of Overly Tight Coupling

Here’s an example of an architecture with overly tight coupling:

In this design, the Food object has to call different methods for the different utensils that are used to eat it. This is a coupling problem, because every time you add a new utensil, you also have to add a new method to the Food object. For example, if you add a Chopsticks utensil, you’ll also have to add an eatWithChopsticks method to the Food object.

Furthermore, the utensil object has to know what kind of food it’s eating to be able to call its operate method. One object typically needs to know something about another to use it, but if that knowledge goes both ways, that’s probably a coupling problem.

So, these two objects are too tightly coupled. The Food object should know as little as possible about what utensil it’s using to be eaten, and the different utensil objects don’t need to know anything at all about the type of food they are eating.

How to Fix It

We can fix these problems by decoupling the Food object from the Utensil object and its subclasses.

We’ll start by replacing all the eatWith methods with a single eat method. We’ll let this method do the eating, instead of calling Utensil‘s operate method and passing the current Food instance to it.

To let the Food object know what utensil it’s using, we’ll store an instance of a Utensil object or one of its subclasses to a property of the Food object when we construct it. The eat method can use whatever utensil it’s given, and doesn’t have to know anything about it beyond that it’s some sort of Utensil instance with an action and a name property.

Finally, we’ll get rid of the Utensil‘s operate method, since we no longer need it.

Here’s the result:

This considerably simplifies the structure. The Food object no longer has to know about different ways it can be eaten, so it can focus on the concern of getting eaten. The instances no longer have to call different methods for different ways of eating food (eatWithFork, eatWithSpoon, eatWithChopsticks, etc.); they can all call the same eat method. Also, adding new Utensil classes such as the Chopsticks class no longer requires any changes to the Food class.

Correlation Between Cohesion and Coupling

You may have noticed that the tightly coupled example also exhibits poor cohesion. Cohesion and coupling issues often go together, because both issues stem from insufficient separation of concerns.

Here, the Food object has to do different things depending on which utensil to use while it’s being eaten. This strays from its chief purpose of just being a food item that gets eaten, and violates the cohesion principle of sticking to one thing.

It’s important to keep cohesion and coupling in mind when designing a class architecture. Cohesive, loosely coupled classes are easier to use, perform better, and are easier to alter and change when implementing changes to business requirements.

For further reading, see cohesion and coupling.