System design and maintainability in software engineering is greatly influenced by cohesion and coupling. Cohesion refers to the relationship between modules, while coupling describes how closely knit a single module’s responsibilities are. For the purpose of creating modular software that is easy to maintain, we must understand these concepts well. Good software design minimises coupling and maximises cohesion in order to ensure adaptability and dependability.
This blog will explore definitions of these terms, types, benefits, and drawbacks. In addition, it shall also provide practical code examples showing how they differ from each other clearly.
What is Coupling?
Coupling in software engineering refers to the degree of direct interdependence between modules. When modules are highly dependent on each other, they are said to be tightly coupled. Conversely, if modules can function independently, they are considered loosely coupled. High coupling can make systems difficult to maintain and evolve because changes in one module may require changes in another. Low coupling, on the other hand, enhances modularity and allows for easier updates and maintenance.
Types of Coupling With Examples
There are a few types of coupling, and we have covered some of them here.
Content Coupling
Content coupling occurs when one module directly modifies or relies on the internal workings of another module. This type of coupling is the most restrictive and should be avoided, as it creates strong dependencies between modules, making the system harder to maintain and evolve.
Example:
# Module A
class A:
def __init__(self, b):
# Module A is initialised with an instance of Module B
self.b = b
def modify_b(self):
# Module A directly modifies the internal data of Module B
self.b.internal_data = 10
# Module B
class B:
def __init__(self):
# Module B has an internal data attribute
self.internal_data = 0
b = B()
a = A(b)
a.modify_b()
print(b.internal_data) # Output: 10
In the example above, Module A directly accesses and modifies the internal data of Module B, creating a strong dependency between the two modules.
Common Coupling
Common coupling occurs when multiple modules share global data. This can lead to issues when different modules unintentionally interfere with each other by modifying the shared data. It increases the risk of side effects and makes it harder to understand and debug the system.
Example:
# Global data
global_variable = 0
# Module A
def function_a():
global global_variable
# Module A modifies the global variable
global_variable += 1
# Module B
def function_b():
global global_variable
# Module B also modifies the global variable
global_variable += 2
function_a()
function_b()
print(global_variable) # Output: 3
In this example, both function_a and function_b modify a global variable, leading to potential unintended side effects and dependencies between the modules.
Control Coupling
Control coupling happens when one module controls the behaviour of another by passing it information on what to do. This type of coupling reduces the autonomy of the receiving module and can make the system harder to maintain and understand.
Example:
# Module A
def function_a(control_flag):
# Module A controls the behaviour of Module B through the control flag
if control_flag == “start”:
function_b(“initialise”)
elif control_flag == “stop”:
function_b(“shutdown”)
# Module B
def function_b(command):
# Module B’s behaviour is dictated by the command it receives
if command == “initialise”:
print(“System Initializing”)
elif command == “shutdown”:
print(“System Shutting Down”)
function_a(“start”) # Output: System Initialising
Here, function_a passes a control flag to function_b, dictating its behaviour and creating a dependency between the two functions.
Stamp Coupling
Stamp coupling occurs when modules share a composite data structure but only use a part of it. This can lead to inefficiencies and makes understanding module dependencies more complex. It can also result in unnecessary data being passed around.
Example:
# Composite data structure
class Data:
def __init__(self, field1, field2):
# The Data class has multiple fields
self.field1 = field1
self.field2 = field2
# Module A
def function_a():
# Module A creates an instance of the Data class
data = Data(10, 20)
# Module A passes the entire data object to Module B
function_b(data)
# Module B
def function_b(data):
# Module B only uses part of the data object
print(data.field1)
function_a() # Output: 10
In this example, function_a passes an entire Data object to function_b, even though function_b only uses field1.
Data Coupling
Data coupling happens when modules share data through parameters. This type of coupling is the least restrictive and generally preferred. It allows for clear and direct communication between modules without creating strong dependencies.
Example:
# Module A
def function_a():
# Module A passes a specific value to Module B
value = 5
function_b(value)
# Module B
def function_b(value):
# Module B uses the value passed from Module A
print(value)
function_a() # Output: 5
In this example, function_a and function_b communicate through a parameter, making their interaction clear and direct without unnecessary dependencies.
Message Coupling
Message coupling is when modules communicate using message passing or events. This type of coupling is even less restrictive than data coupling because it allows modules to be more independent. Modules interact by sending and receiving messages, which can be more flexible and scalable.
Example:
class Event:
def __init__(self, message):
self.message = message
class A:
def send_message(self, message):
event = Event(message)
b = B()
b.receive_message(event)
class B:
def receive_message(self, event):
print(event.message)
a = A()
a.send_message(“Hello, World!”) # Output: Hello, World!
In this example, Module A sends a message to Module B through an event, which Module B processes independently.
Message Coupling
Message coupling is when modules communicate using message passing or events. This type of coupling is even less restrictive than data coupling because it allows modules to be more independent. Modules interact by sending and receiving messages, which can be more flexible and scalable.
External Coupling
External coupling occurs when modules depend on external systems or devices. This type of coupling can be problematic if the external dependencies change or become unavailable, leading to potential system failures or the need for significant adjustments.
Hybrid Coupling
Hybrid coupling is a combination of two or more types of coupling. This type of coupling can introduce complexities in understanding and maintaining the system, as it involves multiple ways in which modules are interdependent.
Logical Coupling
Logical coupling happens when modules are grouped logically but not necessarily functionally. This type of coupling can lead to misunderstandings about module responsibilities and dependencies, making the system harder to manage.
Interface Coupling
Interface coupling occurs when modules share an interface definition but not the implementation details. This type of coupling allows modules to interact without needing to know each other’s inner workings, promoting modularity and ease of maintenance.
Temporal Coupling
Temporal coupling exists when two or more modules must be executed in a specific time sequence. This type of coupling can make the system rigid and difficult to modify, as changes to the timing of one module may affect others.
Procedural Coupling
Procedural coupling occurs when modules share a procedural sequence of actions. This type of coupling can create dependencies based on the order of execution, which can be restrictive and hard to change.
Dynamic Coupling
Dynamic coupling occurs when modules interact at runtime based on dynamic conditions or data. This type of coupling can be flexible but also unpredictable, making the system’s behaviour harder to anticipate and test.
Passive Coupling
Passive coupling happens when modules passively share data through intermediaries, such as databases or files, without direct interaction. This type of coupling can lead to hidden dependencies and potential data consistency issues.
Advantages of Low Coupling
Low coupling in software design ensures that modules are minimally dependent on each other. This enhances modularity and makes the system easier to maintain and update.
- Improved Maintainability: Changes in one module are less likely to affect other modules.
- Enhanced Modularity: Each module can be developed, tested, and understood independently.
- Better Reusability: Modules with low coupling can be reused in different systems or contexts without modification.
- Easier Debugging and Testing: Isolated modules make it simpler to identify and fix issues.
- Increased Flexibility: Low coupling allows for easier updates and changes to the system without widespread impact.
Disadvantages of High Coupling
High coupling in software design means that modules are highly dependent on each other. This can make the system complex and difficult to manage.
- Increased Complexity: Tight dependencies make the system harder to understand and modify.
- Harder Maintenance: Changes made on one module often require corresponding changes on dependent modules.
- Reduced Flexibility: The system becomes rigid and less adaptable to changes.
- Lower Reusability: Highly connected modules are difficult to reuse across various contexts
- Difficult Debugging and Testing: Due to intertwined dependencies, identifying and fixing issues is challenging.
Also Read: COCOMO Model in Software Engineering
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure
What is Cohesion?
Cohesion in software engineering is how much its parts belong together within a given module. Modules that have high levels of cohesion mean that their roles are closely related to one another or confined to one specific function. Conversely, low cohesion means having modules with no clear focus or whose duties cannot easily be streamlined into one particular activity or sub-task.
High-cohesion enables systems that can be understood and maintained more easily. When a module has high cohesion, it becomes easier for someone else to reason about, test or even change it at any time if the need arises. It is also possible for reusing modules which are cohesive in nature because they can be adapted for different uses without significant modification.
Types of Cohesion With Examples
In this section, we have covered a few types of cohesion with code examples.
Functional Cohesion
Functional cohesion occurs when the elements of a module are grouped together because they all contribute to a single well-defined task.
Example:
def calculate_area_of_circle(radius):
# Calculates the area of a circle
return 3.14 * radius * radius
# Example usage
print(calculate_area_of_circle(5)) # Output: 78.5
In this example, the calculate_area_of_circle function is focused solely on calculating the area of a circle, demonstrating high functional cohesion.
Sequential Cohesion
Sequential cohesion happens when elements of a module are grouped because the output of one element serves as the input to another.
Example:
def read_data(file_path):
# Reads data from a file
with open(file_path, ‘r’) as file:
return file.read()
def process_data(data):
# Processes the read data
return data.upper()
def write_data(file_path, data):
# Writes processed data to a file
with open(file_path, ‘w’) as file:
file.write(data)
# Example usage
data = read_data(‘input.txt’) # Reads data from input.txt
processed_data = process_data(data) # Processes the read data
write_data(‘output.txt’, processed_data) # Writes the processed data to output.txt
Here, the output of read_data is used as input for process_data, and the output of process_data is used as input for write_data, showing sequential cohesion.
Communicational Cohesion
Communicational cohesion occurs when elements of a module are grouped because they operate on the same data.
Example:
def process_report(data):
# Processes the report data
processed_data = data.upper()
print_report(processed_data)
def print_report(data):
# Prints the processed report data
print(data)
# Example usage
report_data = “this is a report”
process_report(report_data) # Output: THIS IS A REPORT
In this example, both process_report and print_report operate on the same data, demonstrating communicational cohesion.
Procedural Cohesion
Procedural cohesion happens when elements of a module are grouped because they are part of a sequence of steps to be followed.
Example:
def initialize_system():
# Initialises the system
print(“System Initialized”)
def load_data():
# Loads data into the system
print(“Data Loaded”)
def start_processing():
# Starts processing the data
print(“Processing Started”)
def main():
# Follows a sequence of initialization, loading data, and starting processing
initialize_system()
load_data()
start_processing()
# Example usage
main()
Here, initialize_system, load_data, and start_processing are part of a sequence of steps, showing procedural cohesion.
Temporal Cohesion
Temporal cohesion occurs when elements are grouped together because they are related by the time at which they are executed.
Example:
def initialize_system():
# Initialises the system at startup
print(“System Initialized”)
def initialize_logging():
# Initialises logging at startup
print(“Logging Initialized”)
def initialize_configuration():
# Initialises configuration at startup
print(“Configuration Initialized”)
def startup():
# Performs several initialization tasks that need to happen at startup
initialize_system()
initialize_logging()
initialize_configuration()
# Example usage
startup()
In this example, initialize_system, initialize_logging, and initialize_configuration are all executed at startup, demonstrating temporal cohesion.
Logical Cohesion
Logical cohesion happens when elements of a module are grouped because they perform similar activities, even if they are functionally different. For example, a module that contains various utility functions for string manipulation.
Coincidental Cohesion
Coincidental cohesion occurs when elements are grouped arbitrarily without any meaningful relationship. This is the lowest form of cohesion and should be avoided. An example could be a module that contains unrelated functions like error logging and file compression.
Class Cohesion
Class cohesion refers to the degree to which the methods and properties of a class are related to each other. High-class cohesion means that all elements of the class contribute to a single well-defined purpose.
Object Cohesion
Object cohesion is similar to class cohesion but focuses on the cohesion of an object instance at runtime. High object cohesion means that all operations performed by an object are related to its state.
Layer Cohesion
Layer cohesion occurs in layered architectures, where each layer groups related functionalities. High layer cohesion ensures that each layer has a well-defined responsibility, like presentation, business logic, or data access.
Domain Cohesion
Domain cohesion happens when elements are grouped because they belong to the same domain or context. For example, a module that handles all aspects of user management, including authentication, profile management, and permissions.
Subsystem Cohesion
Subsystem cohesion refers to the degree to which modules within a subsystem are related and work together to achieve a specific subsystem-level functionality. High subsystem cohesion makes the subsystem easier to understand and maintain.
Configuration Cohesion
Configuration cohesion happens when elements are grouped because they are part of the configuration settings of a system. High configuration cohesion ensures that all configuration-related tasks are handled in a single place.
Communication Cohesion
Communication cohesion occurs when elements are grouped because they communicate with the same external system or interface. For example, a module that handles all interactions with a third-party API.
Advantages of high cohesion
High cohesion in software design means that the elements within a module are closely related and focused on a single task or purpose. This brings several benefits to the system.
- Easier Maintenance: Highly cohesive modules are simpler to maintain because they have a clear, well-defined purpose.
- Better Understandability: Modules with high cohesion are easier to understand and reason about, which reduces the learning curve for new developers.
- Enhanced Reusability: A highly cohesive module can be reused across different projects since it performs a specific, well-defined function.
- Improved Debugging and Testing: Cohesive modules are easier to test and debug because they encapsulate a single responsibility.
- Increased Modularity: High cohesion contributes to modular design, where each module operates independently, enhancing the overall system architecture.
Disadvantages of Low Cohesion
Low cohesion in software design indicates that a module performs a variety of unrelated tasks. This can lead to several issues that affect the system’s quality and maintainability.
- Poor Modularity: Modules with low cohesion often have multiple, unrelated responsibilities, making the system less modular.
- Increased Difficulty in Maintenance: Maintaining a module that handles many different tasks is more challenging because changes in one part can affect other unrelated parts.
- Harder to Understand: Low cohesion makes it difficult for developers to understand the module’s purpose and functionality, leading to a steeper learning curve.
- Reduced Reusability: Modules with low cohesion are harder to reuse in different contexts because they perform too many unrelated functions.
- Complex Debugging and Testing: Debugging and testing a module with low cohesion is complex because the module does not encapsulate a single responsibility, making it harder to isolate and fix issues.
Difference Between Coupling and Cohesion
Aspect |
Coupling |
Cohesion |
Definition |
Degree of dependence between modules |
The degree to which elements within a module belong together |
Focus |
Interaction between different modules |
Responsibilities within a single module |
Ideal Scenario |
Low coupling |
High cohesion |
Effect on Maintenance |
Low coupling makes maintenance easier |
High cohesion makes maintenance easier |
Effect on Modularity |
Low coupling enhances modularity |
High cohesion enhances modularity |
Impact on Reusability |
Low coupling improves reusability |
High cohesion improves reusability |
Debugging and Testing |
Easier with low coupling |
Easier with high cohesion |
System Flexibility |
More flexible with low coupling |
More focused and reliable with high cohesion |
Examples |
Data coupling, message coupling |
Functional cohesion, sequential cohesion |
Unwanted Scenario |
High coupling |
Low cohesion |
Key Benefit |
Reduces interdependencies between modules |
Increases the clarity and purpose of each module |
Main Challenge |
Managing dependencies |
Ensuring all elements serve a common goal |
Relationship |
Inverse relationship: As coupling decreases, cohesion tends to increase |
Direct relationship: As cohesion increases, module quality improves |
Conclusion
In summary, understanding coupling and cohesion is essential for designing modular and maintainable software. Coupling refers to the degree of interdependence between modules, with low coupling being ideal for easier maintenance and flexibility. Cohesion, on the other hand, measures how closely related the responsibilities within a module are, with high cohesion leading to better understandability, reusability, and ease of maintenance.
By striving for low coupling and high cohesion, software engineers can create robust, scalable, and maintainable systems. These principles not only improve the quality of the code but also make the development process more efficient and manageable. Applying these concepts effectively ensures that the software can adapt to changes and grow with evolving requirements.
FAQs
Coupling refers to the degree of dependence between modules in a software system. Low coupling is preferred as it makes the system more modular and easier to maintain.
Cohesion measures how closely related the functions within a single module are. High cohesion means the module has a well-defined purpose, making it easier to maintain and understand.
Low coupling is important because it reduces dependencies between modules, making the system easier to modify, test, and maintain.
High cohesion is desirable because it ensures that a module performs a single task or a set of related tasks, which enhances understandability, maintainability, and reusability.
Coupling and cohesion significantly impact software design by influencing modularity, maintainability, and flexibility. Low coupling and high cohesion lead to better-structured and more maintainable software systems.