Everything to Know About Cohesion and Coupling with Examples

Learn how to write better code using cohesion and coupling.

Picture of Nsikak Imoh, author of Macsika Blog
The text Everything to Know About Cohesion and Coupling with Examples written on a white background
The text Everything to Know About Cohesion and Coupling with Examples written on a white background

Table of Content

In this post, you will learn how to write better code by looking at two important qualities in software engineering called cohesion and coupling.

First, I will explain what coupling and cohesion are with examples in software engineering.

Then you will learn a bit about the typical relationship between coupling and cohesion and the best approach in design patterns in coding with coupling and cohesion.

We will use Python to show detailed examples of coupling and cohesion in software engineering and how to improve your codes with it.

However, you can also apply this technique to coupling and cohesion design patterns in Java, C++, and other object-oriented languages.

Lastly, you learn about the advantages and disadvantages of coupling and cohesion in software engineering. And use it to write high-quality, maintainable, and reusable codes.

Suggested Post: What is the Difference Between Encapsulation and Information Hiding in OOP?

What is Cohesion with Examples?

Cohesion is the level at which members of a class or function in a code belong together and interact.

For example, take a look at the function below:


def do_many_things(one_thing: ThingType) -> None:
	handle_first random_tasks()
	handle_second_random_tasks()
	handle_third_random_tasks()
	...
Low Cohesion code example.

Right off the bat, the function name should be a red flag about its cohesion.

If you need me to say it explicitly, this function has weak cohesion.

What is Weak Cohesion?

Weak cohesion is a type of cohesion that occurs when a particular function or method is defined to handle arbitrary tasks or perform an arbitrary number of tasks or responsibilities at once.

What is a Real-Life Example of Weak Cohesion?

A real-life example of weak cohesion is a stuffed doll.

All parts of a stuffed doll are deeply nested and fixed such that when taken apart, there will be a negative cascading effect of changes.

Code Example of a Weak Cohesion

The function above is an example of a weak cohesion in a software design.

It does a lot of different things that do not belong together.

So, you might be thinking, what does it mean to have strong cohesion?

What is Strong Cohesion?

Strong cohesion is a type of cohesion that occurs when a particular function or method is defined to have a clear responsibility and handle a single straightforward task.

What is a Real-Life Example of Strong Cohesion?

A real-life example of strong cohesion is a chain saw.

It consists of a motor, a power supply, a trigger, a handle, a blade for the cutting bits, and the cutting chains themselves.

All of these separate components — as different as they are — work together to create something useful.

Code Example of Strong Cohesion

An example of a strong cohesion is the functions in the Python math library such as the lcm(), gcd(), and all the trigonometric functions such as sine, cosine, and tangent.

They all perform a single task.

For example, the lcm() function in the Python math library gets the lcm of the given parameter and nothing else.

What is the Advantage of Strong Cohesion Over Weak Cohesion?

  • It makes your code easy to maintain.
  • It makes it easy to understand the purpose of the code.
  • It makes it easy to reuse the code.

How do you Improve Coupling in Programming?

We need to refactor the codes from weak to strong cohesion.

And define the code such as functions or methods to have a clear responsibility.

Doing this makes our code easy to maintain, understand, and reuse.

Suggested Post: 5 Tips to Avoid Being Blocked or Blacklisted During Web Scraping

What is Coupling?

Coupling is a measure of how the dependence of each separate part of your code on each other.

Take a look at this example:


def checkPasswordStrength(password: str) -< None:
    if password.isalnum():
        if not has_special_character(password):
            raise Exception("Password must conatin a special character!")
        elif password.islower():
            raise Exception("Please enter one capital letter.")
        elif len(password) < 8:
            raise Exception("Password is too short!")
        elif password.password_bank.similar_password():
            raise Exception("Password is too common!")
        else:
            print("Ooh lala, what a sweet password!")
    else:
        raise Exception("Please provide at least one number and a text!")
High Coupling code sample.

This function checks whether a password is strong by looking at various parameters of a strong password such as length, alphanumeric, and special character.

The code in the function directly accesses some functions that are a core part of Python string functions as well as custom pure password-only functions.

This means that this function is highly coupled to the password object.

However, having high coupling is problematic to our codes.

It is because when we change something in one part of our program, we will need to change things in multiple places.

What is a High Coupling?

The high coupling also called heavy coupling is a type of coupling that occurs when codes are so dependent on each other such that a change in one place can have unknown cascade effects in other places except we trace it and make those changes as well.

High coupling gives rise to code that is harder to understand because complex, intertwined relationships are difficult to understand.

What is a Real World Example of High Coupling?

A real-world example of high coupling is a submarine.

A submarine is made up of hundreds of thousands — if not millions — of unique parts that fit together in one way and one way only.

The parts are most times not reusable, and if you need to a change specific part — especially if you have to alter an interface of a part — you may encounter a problem because the other parts of the submarine’s interface will very likely need to be changed as well.

Code Example of High Coupling

The code above is an example of high coupling.

If the structure of the password object changes, we will have to rewrite some functionalities or risk having to produce unwanted effects.

Of course, your code cannot be completely decoupled.

A collection of completely decoupled modules cannot do anything.

Now, there will always be some coupling in your code, because the various parts of our code need to work together somehow.

But the more coupling you introduce, the more difficult it makes our to maintain.

They need to be coupled in a thinly and lightly.

To solve the coupling issues, we can apply weak coupling.

What is Weak Coupling?

The weak coupling also called loose coupling is a type of coupling that occurs when codes are weakly dependent on each other such that it will be easier to understand, maintainable, easier to read, and easy to modify without causing a negative cascading effect when changes are made.

What is a Real World Example of Weak Coupling?

A real-life example of weak or loose coupling is a submarine built out of Legos.

The different parts can be easily modified and reused as Lego pieces are easily put together and taken apart to make whatever you want.

What is the Advantage of Weak Coupling Over High Coupling?

  • Code will be easier to read and understand.
  • Code will be reusable because it will be connected only to thin portable components.
  • Code will be easy to modify without causing a cascading effect of changes
  • Code will be easy to maintain

How do you Improve Coupling?

We need to make the codes less dependent on each other such that a change in one part of the code is independent of the others.

In the case of our codes above, we could either pass along only the data that the function needs instead of the entire password object.

Or, we could move this function inside the password object since it is so directly related to the data.

What is the Difference Between Cohesion and Coupling? Which is Better?

Short answer: they both compliment each other like bread and jam. More bread and less jam.

If you are still wondering what the best approach is in design patterns in coding with coupling and cohesion, here is all you need to know.

We need to minimize coupling and maximize cohesion by designing and developing a software system that has strong cohesion and weak coupling.

Not only is loose coupling and high cohesion good for object-oriented programming, but it also helps to design and develop software that is portable and scalable.

In addition, it helps to build software with components that are easy to understand by several programmers, reuse in different areas of the software, and maintain when the software dependencies get an update.

What is the Relationship Between Coupling and Cohesion?

They are equally important and can both be used to write better codes in software engineering.

They both serve to ensure that we write codes and design software that is easy to read, understand, maintain, and alter without affecting other components.

As we saw in the previous sections on coupling and cohesion, we do not want to achieve cohesion at the expense of coupling.

The well-designed system is properly coupled using loose coupling and has good cohesion.

Why is Coupling and Cohesion Important in Software Engineering?

Coupling and cohesion are metrics for evaluating the quality of software or a library.

Unfortunately, we cannot simply compute a number that tells you how cohesive or how coupled our code is.

It comes down to you, as a developer, to understand the structure of your code well.

And be able to analyze your code in terms of cohesion and coupling and improve it in general.

Learn to make the right decisions when designing a piece of software.

You will get better at this as you become a more experienced software developer, but it is a skill.

Suggested Post: Build a Text Paraphraser Using Python with Pegasus Transformer for NLP

Explain Coupling and Cohesion in Software Engineering with Code Example and Use Cases

Example Before Coupling and Cohesion


    import string
    import random
    
    class VehicleRegistry:
    
        def generate_vehicle_id(self, length):
            return ''.join(random.choices(string.ascii_uppercase, k=length))
    
        def generate_vehicle_license(self, id):
            return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}"
    
    
    class Application:
    
        def register_vehicle(self, brand: string):
            # create a registry instance
            registry = VehicleRegistry()
    
            # generate a vehicle id of length 12
            vehicle_id = registry.generate_vehicle_id(12)
    
            # now generate a license plate for the vehicle
            # using the first two characters of the vehicle id
            license_plate = registry.generate_vehicle_license(vehicle_id)
    
            # compute the catalogue price
            catalogue_price = 0
            if brand == "Tesla Model 3":
                catalogue_price = 60000
            elif brand == "Volkswagen ID3":
                catalogue_price = 35000
            elif brand == "BMW 5":
                catalogue_price = 45000
    
            # compute the tax percentage (default 5% of the catalogue price, except for electric cars where it is 2%)
            tax_percentage = 0.05
            if brand == "Tesla Model 3" or brand == "Volkswagen ID3":
                tax_percentage = 0.02
    
            # compute the payable tax
            payable_tax = tax_percentage * catalogue_price
    
            # print out the vehicle registration information
            print("Registration complete. Vehicle information:")
            print(f"Brand: {brand}")
            print(f"Id: {vehicle_id}")
            print(f"License plate: {license_plate}")
            print(f"Payable tax: {payable_tax}")
    
    app = Application()
    app.register_vehicle("Volkswagen ID3")
Code Example Before Coupling and Cohesion.

We have a VehicleRegistry and an Application class.

VehicleRegistry class is just a container for two helper methods.

One method helps you generate an ID for a vehicle. It takes a length parameter as the second method to create a license, and you have to give it the ID as a parameter, and then it gives you a license plate number back.

The Application class has a single method called register_vehicle() that takes a brand name.

The vehicle method does a couple of things.

So first, it creates an instance of this VehicleRegistry class.

Then it generates a vehicle ID of length 12, and it uses that ID to create a license plate next.

It calculates the catalog price of that brand of car. So, depending on the brand, you choose a different price.

Another thing is the tax percentage. By default, it is five percent if it is an electric car. However, if it is a Tesla model 3 or Volkswagen id3, the tax percentage is two percent.

That is the overall structure of this simple application we will be using as an example.

Explain the Problem in Code with High Coupling and Weak Cohesion

As you can see, the register_vehicle() method in the Application class is doing different things.

It creates an ID and a license plate. It calculates a catalog price tax percentage. Then it calculates text, and it prints out all the registration information.

It means that this method has very low cohesion. It has way too many responsibilities.

Directly relying on implementation details of the VehicleRegistry class, makes it have a high coupling.

In this example, register_vehicle() has to know that the instance of the VehicleRegistry class generates an ID and that you have to pass that ID to create a vehicle license.

Due to this high coupling, if we change anything in the vehicle registry, we will also have to change the register vehicle method.

Other problems in the code occur because of this low cohesion.

For example, at the moment, it is hard to add another brand of a vehicle because you have to sift through this.

Overall, this is not an efficient way to write good codes.

So, we will try to make this code a lot cleaner and better by looking at the cohesion and coupling metrics.

Fixing the Problem in Code with High Coupling and Weak Cohesion

First, look at where information is stored and how it is accessed.

When you have defined the logical structure of your information, you can also group the code, which leads to weak coupling.

It happens because the code is closer to the information it uses and allows methods with higher cohesion.

Looking at this example, you will see that the data is not stored.

Earlier, we saw a direct coupling between brand name and catalog prices, which is awful.

Also, if you analyze how the tax percentage gets calculated, the tax percentage depends on whether a brand is electric or not and not on this specific brand name.

Another issue is that we have brand information, such as the catalog price of a BMW 5. We also have vehicle instance information, which is an ID of a vehicle or a license plate of a vehicle.

It is probably a good idea to separate these things. So we have one place where we have vehicle brand information that we use to register specific vehicles.

We will create a class called VehicleInfo to hold the information we need.


import string
import random
class VehicleInfo:
    
    def __init__(self, brand, electric, catalogue_price):
        self.brand = brand
        self.electric = electric
        self.catalogue_price = catalogue_price

    def compute_tax(self):
        tax_percentage = 0.05
        if self.electric:
            tax_percentage = 0.02
        return tax_percentage * self.catalogue_price

    def print(self):
        print(f"Brand: {self.brand}")
        print(f"Payable tax: {self.compute_tax()}")
Current state of the vehicle info.

From the code above, an instance of VehicleInfo class will have a brand name.

It also has a catalog price and to solve the problem with determining the tax percentage, we should also store whether this is an electric car brand.

Next, we will create a class called Vehicle to hold the instance of each of the vehicles.


class Vehicle:

    def __init__(self, id, license_plate, info):
        self.id = id
        self.license_plate = license_plate
        self.info = info

    def print(self):
        print(f"Id: {self.id}")
        print(f"License plate: {self.license_plate}")
        self.info.print()
Current state of the vehicle class.

Recall that a vehicle has an ID that would be used to generate a license plate, and we are also going to keep a reference to the actual brand information so that we know what kind of a vehicle this is.

Next, we will move the vehicle registry instantiation and everything about creating and registering a vehicle to the VehicleRegistry class.


class VehicleRegistry:

    def __init__(self):
        self.vehicle_info = { }
        self.add_vehicle_info("Tesla Model 3", True, 60000)
        self.add_vehicle_info("Volkswagen ID3", True, 35000)
        self.add_vehicle_info("BMW 5", False, 45000)
        self.add_vehicle_info("Tesla Model Y", True, 75000)

    def add_vehicle_info(self, brand, electric, catalogue_price):
        self.vehicle_info[brand] = VehicleInfo(brand, electric, catalogue_price)

    def generate_vehicle_id(self, length):
        return ''.join(random.choices(string.ascii_uppercase, k=length))

    def generate_vehicle_license(self, id):
        return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}"

    def create_vehicle(self, brand):
        id = self.generate_vehicle_id(12)
        license_plate = self.generate_vehicle_license(id)
        return Vehicle(id, license_plate, self.vehicle_info[brand])
Current state of the vehicle registry class.

Lastly, we clean-up the Application class and move the final print function to the instantiation point.


class Application:

    def register_vehicle(self, brand: string):
        # create a registry instance
        registry = VehicleRegistry()

        vehicle = registry.create_vehicle(brand)

        # print out the vehicle information
        vehicle.print()

app = Application()
app.register_vehicle("Volkswagen ID3")
Current state of the Application class.

Recap of What We Have Done


    import string
    import random
    
    class VehicleInfo:
        
        def __init__(self, brand, electric, catalogue_price):
            self.brand = brand
            self.electric = electric
            self.catalogue_price = catalogue_price
    
        def compute_tax(self):
            tax_percentage = 0.05
            if self.electric:
                tax_percentage = 0.02
            return tax_percentage * self.catalogue_price
    
        def print(self):
            print(f"Brand: {self.brand}")
            print(f"Payable tax: {self.compute_tax()}")
    
    class Vehicle:
    
        def __init__(self, id, license_plate, info):
            self.id = id
            self.license_plate = license_plate
            self.info = info
    
        def print(self):
            print(f"Id: {self.id}")
            print(f"License plate: {self.license_plate}")
            self.info.print()
    
    
    class VehicleRegistry:
    
        def __init__(self):
            self.vehicle_info = { }
            self.add_vehicle_info("Tesla Model 3", True, 60000)
            self.add_vehicle_info("Volkswagen ID3", True, 35000)
            self.add_vehicle_info("BMW 5", False, 45000)
            self.add_vehicle_info("Tesla Model Y", True, 75000)
    
        def add_vehicle_info(self, brand, electric, catalogue_price):
            self.vehicle_info[brand] = VehicleInfo(brand, electric, catalogue_price)
    
        def generate_vehicle_id(self, length):
            return ''.join(random.choices(string.ascii_uppercase, k=length))
    
        def generate_vehicle_license(self, id):
            return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}"
    
        def create_vehicle(self, brand):
            id = self.generate_vehicle_id(12)
            license_plate = self.generate_vehicle_license(id)
            return Vehicle(id, license_plate, self.vehicle_info[brand])
    
    
    class Application:
    
        def register_vehicle(self, brand: string):
            # create a registry instance
            registry = VehicleRegistry()
    
            vehicle = registry.create_vehicle(brand)
    
            # print out the vehicle information
            vehicle.print()
    
    app = Application()
    app.register_vehicle("Volkswagen ID3")
overall code we wrote compared to the previous.

Look at the overall code we wrote compared to the previous.

We stored the information in a much more logical place by splitting it into vehicle brand information and vehicle instance information. And we attached methods to do something with that particular data.

Looking at each of these functions, we now have quite a few.

Each function is specific in what it does. Each method in this new code has one responsibility.

Of course, there is always some coupling left, because RegisterVehicle class needs to access the VehicleRegistry's create_vehicle() method.

It is easier to handle than the coupling we had in the first version of this program.

The goal of efficient coupling is not to eliminate coupling. It is to minimize it.

It indicates that we have drastically reduced the coupling leading to weak coupling.

To show the improvements in cohesion, functions are now so close to the data that they manage.

So, compute_tax() is part of vehicle info, which already contains the information that you need for computing the tax.

With what we have now, the code is easier to extend or maintain. For example, we can easily add a new car with a single line.

You can also make other changes easily.

For example, if you want to change the tax percentage for electric cars, you can go to compute_tax() method and do it there.

To decouple it further, we could split the compute_tax() method into two methods: one determines the tax percentage, the other calculates the tax to be paid based on the catalog price.

Wrap Off

I hope this code example has helped you understand what cohesion and coupling are and how you can use them to write better code.

The examples in this post are in Python. However, you can apply this to any programming language.

If you enjoyed this post, please consider sharing. And if you want to buy me a coffee. There is a button below specially made for you.

Have a nice one.

Get the Complete Code of Python Code Snippets on Github.

Connect with me.

Need an engineer on your team to grease an idea, build a great product, grow a business or just sip tea and share a laugh?