There are two terms called coupling and cohesion. These concepts can help you understand more about your code and how your actions affect the code. They define relations between and within classes. Knowing them can help you create a better structure for your code, making it more flexible and purposeful. Let's learn about them; starting with coupling.
Coupling
Coupling is the degree to which software components depend on each other. There are two basic levels of coupling: tight and loose.
Loose coupling means less interdependence between classes, whereas in tight coupling it's the opposite. The difference is that in tight coupling, one class strongly depends on another and knows too much about its methods. But, in loose coupling one class knows little about object creation and methods in another class. This makes these classes less interdependent on each other.
The simplest way to loosen coupling is by implementing an interface in your code. There are also other ways to loosen coupling, like dependency injection.
Tight coupling example
Let's assume you're building a robot. A central part of this robot is an engine that cannot be easily replaced. You would have to deconstruct your robot and tweak it to use your new engine. It's a tightly coupled dependency which you can depict as:
class Engine is
method run() is
class Robot is
Engine eng = new Engine()
eng.run()Here, the Robot class is dependent on Engine and needs to create an instance of it. Your dependent class knows too much about methods in this case, which tightens this coupling. Any changes in Engine will force you to change Robot as well. So, if the engine doesn't work, the whole robot will stop functioning.
Loose coupling example
Let's look at an example of loose coupling. Here, you can build a robot with a default paint scheme. It can be either silver or gold. Either way, you have to change your robot from scratch. It's a loosely coupled dependency.
interface Paint is
method paint()
class Silver implements Paint is
method paint() is
print("silver")
class Gold implements Paint is
method paint() is
print("gold")
class Robot is
Paint col = new Gold()
col.paint()The paint classes are exposed to the Robot class through the Paint interface. In this instance, you can inject paint class methods through the interface. This means your Robot class knows little about methods in Silver and Gold classes. The Robot class can also use them separately. This means you can use other classes in case one doesn't work.
Cohesion
Cohesion is a representation of class functions. It is a measure of how closely related the responsibilities of a component are. Low cohesion suggests that a component is trying to perform too many unrelated tasks, making it harder to maintain and understand. In short, the more a class is able to do, the lesser its cohesion. In contrast, high cohesion indicates that a component performs a single task or a group of related tasks, leading to more understandable and maintainable code.
For example, when you're working on an application that represents a robot building factory. You can make a single class called RobotFactory. This class contains methods for robot creation, factory logistics, staff management, and much more.
class RobotFactory is
method createRobots() is ...
method maintainLogistics() is ...
method manage() is ...This is an example of low cohesion where a single class has many different purposes. It is difficult to reuse and test classes like this. But, if you try to distribute methods with different purposes in different classes, you can increase your cohesion.
class BuildDepart is
method createRobots() is
...
class Logistics is
method maintainLogistics() is
...
class Management is
method manage() is
...Working with cohesion and coupling
The chart below illustrates the possible consequences based on the degree of cohesion and coupling.
Ideally, your objects should have low coupling and high cohesion. In other circumstances, you'll probably neglect some basic design principles like the single responsibility principle.
There are also situations when you completely forget about other elements while trying to achieve the ideal state, i.e. low coupling and high cohesion. This can lead to one of the following:
Making a god object (low cohesion, high coupling) — one object performs all functions.
Poorly selected boundaries (high cohesion, high coupling) — fragments of code have boundaries, but they also contain classes that shouldn't be included in them.
Destructive decoupling (low cohesion, low coupling) — parts of code have the lowest coupling possible, but that leads to a lack of focus on your code.
When trying to lower your coupling, you shouldn't forget about increasing your cohesion and vice versa.
Conclusion
Let's summarize what we know about coupling and cohesion.
You should try to achieve loose coupling. Loose coupling makes parts of your code less interdependent on each other. The easiest way you can achieve it is with interfaces and dependency injection.
Parts of your code should have high cohesion, so that they can be more focused. This will make them more reusable and easier to test.
Ideally, you should try to maintain the highest cohesion and lowest coupling levels. It will make your code more readable and accessible.