Updated on 19 Jun, 202633 mins read 30 views

Introduction

Now we know:

  • Abstraction helps us manage complexity
  • Modularity helps us organize software
  • Separation of Concerns helps us divide responsibilities

However, even if we successfully separate our system into modules, another critical appears:

How much should these modules know about each other?

Imagine two software systems.

System A:

User Module
      |
      v
Order Module
      |
      v
Payment Module
      |
      v
Inventory Module

System B:

User Module  <------> Payment Module
     ^    \         /      ^
     |     \       /       |
     |      \     /        |
     v       \   /         |
Order Module <--> Inventory
     ^                    /
     |                   /
     +------------------+

Both systems may work.

But one is dramatically harder to maintain.

Why?

Coupling and Cohesion

These concepts are so important that nearly every software engineering principle, design pattern, architecture style, and framework eventually boils down to improve one or both.

In fact:

Good software design is largely the art of achieving low coupling and high cohesion.

Everything else is often a consequence.

If abstraction helps us reduce complexity, and modularity helps us organize complexity.

then coupling determines:

How complexity spreads through a system.

Historical Context

During the Software Crisis (1960s – 1980s), engineers noticed something strange.

Two systems with similar functionality could have drastically different maintenance costs.

Example:

System A:
100,000 lines

System B:
100,000 lines

Same size.

But:

System A: Easy to modify

System B: Extremely difficult to modify

Why?

Researchers began studying software architecture.

They discovered two characteristics repeatedly appeared in successful systems:

LOW COUPLING
HIGH COHESION

These became fundamental software engineering principles.

Understanding Coupling

What Is Coupling?

Coupling measures:

How strongly one component depends on another component.

Or more simply:

The degree of dependency between two software components.

Simplified:

How much one component knows about another component.

Real-World Analogy: Train Cars

Imagine two apartments.

Loosely Coupled

Neighbors:

Apartment A

Apartment B

They can live independently.

One family moves out.

The other continues normally.

Tightly Coupled

Imagine:

Apartment A shares:

Water
Electricity
Kitchen
Bathroom

with

Apartment B

Now any change affects both apartments.

This is tight coupling.

Software behaves similarly.

Formal Definition

Coupling is:

The degree of dependency between software modules.

Higher dependency:

Higher Coupling

Lower dependency:

Lower Coupling

Why Coupling Exists

An important truth:

Coupling is unavoidable.

No useful system consists of completely independent parts.

Example:

Order Module

must interact with:

Payment Module

Otherwise orders cannot be paid.

The goal is NOT:

Zero Coupling

Impossible.

The goal is:

Manageable Coupling

Why Coupling Matters

Every dependency introduces risk.

Example:

class OrderService
{
private:
    PaymentService paymentService;
};

OrderService depends on PaymentService.

Therefore:

OrderService
      ↓
Depends On
      ↓
PaymentService

This dependency creates coupling.

Visualization Coupling

Low Coupling:

+---------+
| ModuleA |
+---------+

+---------+
| ModuleB |
+---------+

+---------+
| ModuleC |
+---------+

Independent modules.

High Coupling:

A → B → C → D → E
↑               ↓
+---------------+

Complex dependency network.

Changes become dangerous.

Example: Tight Coupling

Consider:

class MySQLDatabase
{
public:
    void save()
    {
    }
};
class UserService
{
private:
    MySQLDatabase database;

public:
    void registerUser()
    {
        database.save();
    }
};

Problem:

UserService depends directly on MySQL.

Suppose tomorrow:

MySQL --> PostgreSQL

Now UserService must change.

This is tight coupling.

A Mental Model

Thinking of coupling as a rope.

Loose Coupling:

Module A

    |
    |

Module B

Small connection.

Tight Coupling:

	Module A
 |||||||||||||||
	Module B

Many connections, Harder to separate.

Tight Coupling

Definition:

A component depends heavily on the internet details of another component.

Example:

class PaymentService
{
public:

    void connectGateway();
    void createRequest();
    void sendRequest();
    void parseResponse();
};

Client:

PaymentService payment;

payment.connectGateway();
payment.createRequest();
payment.sendRequest();
payment.parseResponse();

The client knows too much.

Problems:

Fragile
Hard to Change
Hard to Test
Hard to Replace

Loosely Coupling

Definition:

A component depends only on the essential behavior of another component.

Example:

class PaymentService
{
public:

    void processPayment();
};

Client:

payment.processPayment();

Simple.

Only necessary knowledge exposed.

Benefits:

Easy to Change
Easy to Extent
Easy to Test
Easy to Replace

Signs of Tight Coupling

When reviewing designs, look for:

Sign 1: Direct Object Creation

class OrderService
{
private:

    PaymentService payment;
};

OrderService is tied directly to PaymentService.

Sign 2: Knowledge of Internals

payment.connect();
payment.prepare();
payment.send();
payment.retry();

Too much knowledge.

Sign 3: Ripple Effects

Change:

Payment Module

Breaks:

Orders
Invoices
Reports
Notifications

Strong coupling exists.

Sign 4: Difficult Testing

Testing:

OrderService

requires:

Real Payment Gateway
Real Database
Real Email Service

Tightly coupled system.

Better Design

Introduce abstraction.

class IDatabase {
	public:
		virtual void save() = 0;
};

Implementation:

class MySQLDatabase: public IDatabase {
	public:
		void save() override
		{
			// ...
		}
};

Usage:

class UserService {
	private:
		IDatabase& database;

	public:
		UserService(IDatabase& db)
			: database(db)
		{
		}
};

Now:

UserService depends on abstraction

Coupling decreases significantly.

Symptoms of High Coupling

Frequent Ripple Effects

Change:

Class A

Unexpectedly affects:

Class B
Class C
Class D
Class E

Fear of Modification

Developers say:

Don't touch that code.

Major warning sign.

Difficult Testing

Creating one object requires:

Databases
Network
Cache
Queue
Logger

Too many dependencies.

Difficult Reuse

Class cannot be reused because it depends on everything.

Types of Coupling

1: Content Coupling (Worst)

One module directly modifies internals of another.

Example:

class User
{
public:
    std::string password;
};

External code:

user.password = "345";

Very dangerous.

2: Common Coupling

Shared global state.

globalConfig
globalDatabase
globalLogger

Everything depends on the same data.

Example:

GlobalSettings::debugMode = true;

Suddenly entire sysetm behavior changes.

3: Control Coupling

Passing flags that control behavior.

Example:

generateReport(true);

Question:

What does true mean?

Bad design.

Better:

generateSummaryReport();
generateDetailedReport();

4: Data Coupling

Passing only required information.

calculateTax(price);

Simple.

Clear.

Independent.

Desired Goal

Move toward:

LOW COUPLING

Not:

ZERO COUPLING

Important distinction.

Some dependencies are necessary.

Understanding Cohesion

What Is Cohesion?

Cohesion measures:

How strongly related the responsibilities inside a module are.

Or:

How well things belong together

Real-World Analogy: Toolbox

Good toolbox:

Hammer
Screwdriver
Wrench
Pliers

All related. High cohesion.

Bad toolbox:

Hammer
Pizza
Laptop
Dog Leash
Toothbrush

Random items. Low cohesion.

Software works exactly the same way.

Formal Definition

Cohesion is:

The degree to which elements within a module belong together.

Higher relatedness:

Higher Cohesion

Lower relatedness:

Lower Cohesion

Example: Low Cohesion

class EmployeeManager
{
public:

    void addEmployee();

    void calculateSalary();

    void sendEmail();

    void generateReport();

    void connectDatabase();

    void printInvoice();
};

Question:

What is this class responsible for?

Answer:

Everything

Low cohesion.

Example: High Cohesion

class EmployeeService
{
public:

    void addEmployee();

    void removeEmployee();

    void updateEmployee();
};

Single responsibility. Strong cohesion.

Visualizing Cohesion

Low Cohesion:

EmployeeService

 ├─ Email
 ├─ Database
 ├─ Reports
 ├─ Payroll
 ├─ Authentication

Random responsibilities.

High Cohesion:

EmployeeService

 ├─ Add Employee
 ├─ Remove Employee
 ├─ Update Employee

Everything belongs together.

Why Cohesion Matter

High cohesion improves:

Readability

Maintainability

Testability

Reusability

Because responsibilities are clear.

Cohesion Levels

Coincidental Cohesion

Completely unrelated functionality.

Example:

Utils
Helpers
CommonStuff
MiscFunctions

Typical anti-patterns.

Logical Cohesion

Related only by category.

Example:

class InputHandler
{
    handleKeyboard();
    handleMouse();
    handleTouch();
}

Better, but still weak.

Functional Cohesion

Everything contributes toward one goal.

Example:

class PaymentProcessor
{
    validatePayment();
    authorizePayment();
    capturePayment();
}

Coupling vs Cohesion

CouplingCohesion
Between modulesWithin module
Dependency measurementResponsibility measurement
Want lowWant high
Affects flexibilityAffects clarity

The Golden Rule

The fundamental design goal:

LOW COUPLING

HIGH COHESION

This combination creates maintainable systems.

Expert Notes

One of the strongest heuristics in software design:

Things that change together should live together.

This increses cohesion.

Another:

Things that change independently should be separated.

This decreases coupling.

A useful mental model:

Cohesion pulls things together.

Coupling pushes things apart.

Good design balances both forces.

Most software design principles you will learn later:

SRP
OCP
ISP
DIP
Composition
Design Patterns

are largely mechanisms for improving coupling and cohesion.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *