Principles of Good Modular Design (Coupling, Cohesion & Encapsulation)
Good systems have the following properties:
- Useful and Usable
- Reliable (Low Coupling)
- Flexible (Low Coupling, High Cohesion)
- Easy to modify for different purposes and in event of change.
- Affordable (Software Reuse)
- Available (Decreased development time through reuse.)
Module Interfaces
The interface of a module is how you interact with the module.
Assumptions about interfaces should be documented in the interface:
- This means that changes to the internal of a module shouldn’t change how the interface behaves.
Principles of Good Design
Linguistic Modular Units
This is the idea that modules should correspond to linguistic units (such as classes) in the language used.
Few Interfaces
The overall number of communication channels between modules should be as small as possible.
In a system with $n$ modules there may be a minimum of $n-1$ and maximum of $\frac{n(n-1)}2$ links.
Facade Structure
You can also implement a public interface for a set of modules.
This keeps interfaces down as you can only interact with a package via it’s public interface.
Coupling
This is a measure of the strength of the inter-connection between system component.
Ideally components should exchange as little data as possible between themselves.
-
Loose Coupling - Component changes are unlikely to affect other components.
Loose coupling can be achieved by state decentralisation and component communication via parameters, or message passing.
-
Tight Coupling - Components have shared variables and lots of interaction.
This is only acceptable when using a shared database.
Coupling & Inheritance
Object-oriented systems are loosely coupled because there is no shared state and object communicate using message passing.
-
However, an object class is coupled to its super-classes.
Changes made to the attributes or operations in a super-class propagate to all sub-classes.
Explicit Interfaces
If two modules must communicate, they should do it so that we can see it.
This should be obvious from the documentation of the modules involved.
If we change a module, we need to see if any other modules may be affected by the change. A traceability matrix can be used for this.
Information Hiding (Encapsulation)
All information about a module (and partially how the module doesn’t what it does) should be private to the module unless it is specifically declared otherwise.
Therefore, each modules should have an interface, which is how the world see it. Anything beyond the interface should be hidden.
The default Java rule is to make everything private.
Cohesion
This is a measure of how well a component fits together:
- A component should implement a single logical entity or function.
Levels of Cohesion
Weak:
- Coincidental Cohesion - Parts of a component are simply bundled together.
- Logical Association - Components which perform similar functions are grouped.
- Temporal Cohesion - Components which are activated at the same time are grouped.
Medium:
- Communicational Cohesion - All the elements of a component operation on the same input or produce the same output.
- Sequential Cohesion - The output for one part of a component is the input to another part.
Strong:
- Functional Cohesion - Each part of a component is necessary for the execution of a single function.
- Object Cohesion - Each operation provides which allows object attributes to be modified or inspected.
Ideally you want your cohesion to be as strong as possible with the weakest possible coupling.
Inheriting attributes from super-classes weakens cohesion as you have to examine the super-class as well as each component.
Cohesion vs. Encapsulation
- Cohesion - A measure of abstraction that means developers do not need to concern themselves with the internal working of a module.
- Encapsulation - Means that developers are unable to use hidden information within a modules, ensuring that subtle errors cannot be introduced when using connected modules.
Pluggable Modules
If a module has high cohesion, low coupling and a well defined interface then it may be feasible to reuse that module in other systems.
You should make architectural decisions early as using architecture specific features can make code less reusable.