Click here to Skip to main content
14,599,419 members

Coupling and Cohesion

Rate this:
4.96 (27 votes)
Please Sign up or sign in to vote.
4.96 (27 votes)
18 Apr 2015CPOL
Coupling and Cohesion are two properties that deserve your attention when you design software. These are important because they have a direct effect on how flexible and maintainable your software will be as your software continues to e developed.

Coupling and Cohesion are two properties that deserve your attention when you design software. These are important because they have a direct effect on how flexible and maintainable your software will be as your software continues to e developed. Will you be able to reuse it? Will you be able to adapt it? Will you need a shoe-horn to force that new feature in the future? These questions are much simpler to answer when you can properly gauge the level of Coupling and Cohesion of your components.

interface

Coupling and Cohesion

I believe the source of the terms Coupling and Cohesion is from a paper called, Structured Software Design (1974), Wayne Stevens, Glenford Myers, and Larry Constantine. This paper discusses characteristics of "good" programming practices that can reduce maintenance and modification costs for software.

Loose (Low) coupling and High (Tight) cohesion are the desirable properties for components in software. Low coupling allows components to be used independently from other components. High cohesion increases the probability that a component can be reused in more places, by limiting its capabilities to small well-defined tasks. For those of you keeping score at home, high cohesion is the realization of the S, Single Responsibility, in SOLID object-oriented design.

interface

Coupling

A measure of the degree to which a component depends upon other components of the system.

Given two components X and Y:
If X changes interface interface how much code in Y must be changed?

Low coupling is desired because it allows components to be used and modified independently from one and other. 

The next two rows demonstrate coupling dependencies.
Image 5 interface Image 7 Image 8 Image 9
Image 10 Image 11Image 12 Image 13 Image 14 Image 15

 

Very generic coupling, the most compatible bricks.

Image 16 Image 17 Image 18 Image 19 Image 20 Image 21 Image 22 Image 23 Image 24

 

interface

Cohesion

A measure of how strongly related the various functions of a single software component are.

Given a component W:
Image 26 How focused is W at fulfilling a single responsibility?

High cohesion is desired because:

  • Leads to a simpler minimal class that embodies one concept
  • A minimal class is easier to comprehend and use
  • Therefore it is more likely that this class will be reused 

These pieces demonstrate cohesion over a range of interfaces.

Highest cohesion:

Image 27 Image 28 Image 29 Image 30
  Image 31 Image 32 Image 33 Image 34 Image 35 Image 36
  Image 37
  Image 38 Image 39 Image 40 Image 41 Image 42 Image 43

Lowest Cohesion:

Image 44 Image 45 Image 46

 

interface

Reuse of Code

For decades many great minds have envisioned software development to require much less effort than it requires today. Each new process, concept, language or framework has high hopes to be that solution. For example, consider the number of projects built specifically to make JavaScript more manageable. We want to encapsulate the details by abstracting away the details and providing a clean interface.

It's important to set your expectations for what to consider reusable code. Without a clear set of expectations, how will you know when you have achieved your goals. More importantly, how can you formulate a design strategy or a reproducible process to consistently create reusable code.

Not all code is reusable

To further clarify this statement, not all code should be reused.

Why Not?

Because the majority of software that is written is designed to fulfill a very specific purpose. The challenge is to find the balance point between generic reusable building blocks, and a tool or application that meets a need. Generic software components are very valuable, however, they are almost useless by themselves.

What good is a linked-list without a mechanism to populate the data in the list, process the data to perform useful outcomes, and later inspect the data. Another example could be a button control for a GUI that handles the name, display and animations of the button. However, it is the actions that are triggered by the button that are important to the end user.

Good design and practices should be used for all software that you write. However, recognize and differentiate which portions of your application are generic and likely to be reused compared to the code that is very specialized. Both of these are of equal importance.

The value of reusable code

For every statement of code that you write or use, you should ask the question:

Does this add value or risk?

Reusable code only provides value If it:

  • Is easy to learn and use
  • Requires less work -> time -> money to develop your final target
  • Has already been tested and proven to be robust
  • Results with a project that is at least as robust as the code that it is built upon

If any one of these qualities is undetermined, you may be adding risk along with any perceived value for the reused code.

Increase the potential for code reuse

Here are some design guidelines that can help increase the potential for your code and components to be reusable:

Easy to use correctly, hard to use incorrectly

If you are fan of Scott Meyers', Effective C++, you will probably recognize this from Item 18:

"Ideally, if an attempted use of an interface won't do what the client expects, the code won't compile; and of the code that does compile, it will do what the client wants"

Good interfaces are easy to use correctly and hard to use incorrectly. The example that is used to demonstrate this concept in Effective C++ uses a calendar date class to illustrate the point. Start with a constructor such as this:

class Date
{
public:
  Date(int month, int day, int year);
  ...
};

This is a reasonable declaration if you live in the United States, which uses the format month/day/year. However, most other countries prefer the format year/month/day. Another problem with this interface is there are three integer input parameters. Without refering to some form of documentation, it may be impossible to deduce the order of the parameters. These are some of the possibilities for how the interface could be misused:

Date d1(2015, 30, 3); // Non-US date format
Date d2(30, 3, 2015); // Listing the day first
Date d3(1, 30, 2015); // There's only 28 days in Feb.
                      // On yeah, our months are 0-based.

One possible approach to solving all of the issues above is to create new types to improve clarity and enforce compile-time correctness.

class Month
{
public:
  static Month Jan(){ return Month(1);}
  static Month Feb(){ return Month(2);}
  ...
private:
  explicit Month(int m)
  { ... }
};

Declaring the only constructor privately forces the user to use the static member functions to create new instances of a Month object. Similar definitions could be created for the day and year parameters. The improved usage would look like this:

Date d(Month::Feb(), Day(20), Year(2015));

To encourage correct use, design interfaces that are consistent and behaviorally compatible.

Use these techniques to help prevent errors:

  • Create new types
  • Restrict operations on those types
  • Constrain object values
  • Eliminate client resource management

Create interfaces that are minimal and complete

Strive to create the minimal number of operations in your interface to access the complete behavior of the object. Minimal objects embody one concept at the right level of granularity. Also, minimal interfaces are easier to comprehend, and more likely to be used and reused.

Monolithic classes will generally dilute encapsulation. Instead of doing one thing will, they end up providing many things that are mediocre. It is more difficult to make these objects correct and error-safe because they tackle multiple responsibilities. These types of objects usually result from an attempt to deliver a complete solution for a problem.

Encapsulate - Hide/Protect Information

Encapsulation is one of the most valuable aspects of object-oriented programming. Encapsulation is necessary to create a proper abstract design. The purpose of abstraction is to simplify concepts by hiding the details of the abstraction.

A proper abstraction will handle the dirty details required to achieve a correct and error-free implementation. The hidden details verify the invariants of your object each command and when internal state or data is changed. Abstractions become even more effective when a number of internal variables can be managed, and a reduced set of values are presented through the public interface.

3 made up facts... Eh, but probably true

  1. Programmers like to get the job done
  2. Programmers are inventive
  3. If a programmer finds a way to complete a task, even if it requires them to blatantly ignore the comment below, they will still write to the data; Because it will get the job done (refer to factioid 1)
class HopelesslyCoupledFromTheStart
{
public:
  // This is a very sensitive value, you can read but
  // do NOT write to this value even to get the job done!
  double m_calibrationOffset;
  ...
};

Data Members

Declare all data members private. Private data is teh best means a class can use to protect its invariants now as well as in the future as it adapts to possible changes.

Public data members move the responsibility to maintain and guarantee the invariants from the object to all of the code that accesses the abstraction. Freely providing access to an objects data members effectively negates most of the benefits gained from creating the abstraction.

Metaphorical Thought Experiment

WARNING: Visualize this thought experiment at your own risk.

Next time you are considering creating a public data member, think of the human body as an abstraction of a much more complicated biological organism. One where the designer declared all of the data members public rather than private.

  • Your internal organs would now all be external organs ripe for the picking of any black-market human organ peddeler.
  • All figures of speech related to the body must be interpretted literally: "What's on your mind?"
  • There will be many more Do-it-yourself doctors.
  • Negates the benefits of being declared a friend.
  • Couples do not need to worry about tight coupling, however, they will need to worry about becoming tangled.
  • Humans in space?! Forget it!

The inferiority of non-private data members

Non-private data members are almost always inferior to even simple pass-through getter/setter methods. These methods provide the flexibility in the future to adapt to change as well as these benefits:

  • Verify Invariants
  • Lazy Evaluation / Data Caching
  • Instrumentation
  • Decouple for Future Modifications

Protected data members suffer from the same disadvantages as public data members. The responsibility to maintain the invariants is shared with the unbounded set of objects which includes:

  • Every class currently derived from this class
  • Every class that will be derived from this class in the future

The one exception is the C-style struct or plain-old data (POD), which is used as a behaviorless aggregate and contains no member functions.

Use the weakest coupling relationship possible

Let's examine the types of relationships that are possible in C++. The list below is ordered by a measure of coupling, tightest to loosest:

  • friend-ship
  • public inheritance
  • Nonpublic inheritance
  • Composition (member variable)

Choose the coupling relationship that provides the weakest level of coupling possible. This will provide more flexibility when something needs to be changed in the future.

Prefer writing non-member non-friend functions

This will improve encapsulation by minimising dependencies, therefore, reduces coupling. The function implementation cannot be modified to depend on the private member data of the class. It can only be implemented based on what is publically accessible.

This will also break apart monolithic classes. The separable functionality can then be further encapsulated by composition.

Example: std::string contains 103 public member functions, 71 could be non-member non-friend implementations.

Use inheritance to achieve substitutability

Inheritance is a wonderul tool available with object-oriented programming. However, it should not be selected as a way to reuse code. Instead, use inheritance as a way to facilitate polymorphism.(For those of you keeping score at home, this concept is the L, Liskov substitutability principle, in SOLID object-oriented design.)

When you consider inheritance, seriously contemplate the is-a relationship between your objects. Think of is-a as "Can be substituted with...".

Public inheritance facilitates polymorphism, which implies substitutability:

Original Object ... Can be substituted with: ... Not with:
Image 48 Image 49Image 50Image 51 Image 52Image 53
Image 54 Image 55Image 56 Image 57Image 58Image 59

Working with your objects will feel cumbersome if you inherit without substitutability.

Don't inherit to reuse; inherit to be reused.

    From Item 37 in C++ Coding Standards, Herb Sutter and Andrei Alexandrescu 

Both composition and private inheritance means is-implemented-in-terms-of.

So which one do you use?

Use composition whenever possible. Choose private inheritance when necessary. It becomes necessary when private or protected access to information is required.

Remember, use the solution that creates the weakest coupling relationship:

public Inheritance
Image 60
public BrickX { };
Image 61Image 62Image 63
Composition
Image 64
color_t m_color;
Image 65Image 66Image 67Image 68
More Composition
Image 69
block_t m_blocks[2];
pin_t m_pins[2];
Image 70Image 71 Image 72Image 73

Don't underestimate the power of composition

Robust composition is easier to achieve compared to inheritance, especially with cohesive interfaces.

Image 74
Image 75

Summary

Minding the concepts of coupling and cohesion can lead to more robust software that avoids fragility and is more maintainable.

  • Simple definitions and implementations are easier to comprehend and use
  • Simple objects can be reused to create more useful objects with composition
  • Only use public inheritance when derived objects can truly be substituted for the base object
  • Every solution in a computer program is built upon a collection of much simpler solutions

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul M Watt
Engineer
United States United States
I am a software architect and I have been developing software for nearly two decades. Over the years I have learned to value maintainable solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development. I use the most beneficial short-term achievements to drive the software I develop towards a long-term vision.

C++ is my strongest language. However, I have also used x86 ASM, ARM ASM, C, C#, JAVA, Python, and JavaScript to solve programming problems. I have worked in a variety of industries throughout my career, which include:
• Manufacturing
• Consumer Products
• Virtualization
• Computer Infrastructure Management
• DoD Contracting

My experience spans these hardware types and operating systems:
• Desktop
o Windows (Full-stack: GUI, Application, Service, Kernel Driver)
o Linux (Application, Daemon)
• Mobile Devices
o Windows CE / Windows Phone
o Linux
• Embedded Devices
o VxWorks (RTOS)
o Greenhills Linux
o Embedded Windows XP

I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.

I am the creator of an open source project on GitHub called Alchemy[^], which is an open-source compile-time data serialization library.

I maintain my own repository and blog at CodeOfTheDamned.com/[^], because code maintenance does not have to be a living hell.

Comments and Discussions

 
QuestionMissing Pics from Article Pin
Member 1348721916-Dec-17 23:20
MemberMember 1348721916-Dec-17 23:20 
Generalvery nice Pin
BillW3319-May-15 3:26
professionalBillW3319-May-15 3:26 
QuestionVoted Pin
Sibeesh Passion8-May-15 4:52
professionalSibeesh Passion8-May-15 4:52 
GeneralNice! Pin
Praveen Raghuvanshi20-Apr-15 1:16
professionalPraveen Raghuvanshi20-Apr-15 1:16 
GeneralRe: Nice! Pin
Paul M Watt20-Apr-15 5:10
mentorPaul M Watt20-Apr-15 5:10 
GeneralRe: Nice! Pin
Praveen Raghuvanshi20-Apr-15 19:27
professionalPraveen Raghuvanshi20-Apr-15 19:27 
QuestionSuperb article Pin
Dominic Burford19-Apr-15 0:00
professionalDominic Burford19-Apr-15 0:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Technical Blog
Posted 18 Apr 2015

Tagged as

Stats

33.9K views
19 bookmarked