Click here to Skip to main content
15,878,748 members
Articles / Programming Languages / C#

Cohesion and Coupling: Principles of Orthogonal, Scalable Design

Rate me:
Please Sign up or sign in to vote.
4.98/5 (14 votes)
17 Mar 2017CPOL7 min read 24.6K   14   13
Cohesion and coupling: Principles of orthogonal, scalable design

The sections below are about enabling an application to evolve and be maintained with minimal risks and effort. It is not easy to interpret a lot of complex information derived from the organizational structure of a source code. By separating concerns (link), we minimize complexity. Different responsibilities are maintained in different places. Separation of concerns is about dividing to conquer, about modularity, encapsulation, defining layers, about individual pieces of code that are developed and maintained individually and independently.

Instead of worrying about different sections of the code, we need to focus on localized changes in the right (and expected) places.

Orthogonality

In geometry, Euclidean vectors are orthogonal if they are perpendicular, i.e., form a right angle. Even if these vectors grow infinitely in space, they will never cross. Well designed software are orthogonal. Their components can grow or be modified without affecting other components.

Orthogonal design is built upon two pillars: cohesion and coupling. These concepts form the basis of software design. However, although well known, they are constantly ignored or misunderstood.

Image 1

Coupling

Coupling (also known as Dependency) is a degree to which one program unit (e.g., a class, module, subsystem) relies on other units. It is a measure of strength of the interconnections between elements, which should be minimized.

We want elements that are independent of each other. In other words, we want to develop applications that exhibit loose (rather than tight) coupling.

However, since parts need to communicate among themselves, we do not have completely independent modules. As interconnections grow between the parties involved, one module will need more information about the other, increasing the dependency between them.

Image 2

The code below is a sample of content coupling. It occurs when one component depends (by modifying or relying) on internal data or behavior of another component. Changing elementary structure or behavior of one component leads to refactoring of other components.

C#
public class LoggedUsersController
{
    public Dictionary<int, DateTime> LastUserLoginDateAndTime { get; set; }
    public List Users { get; set; }
}

public class BusinessRule
{
    private LoggedUsersController loggedUsers =
                new LoggedUsersController();

    public User RegisterUserLogin(int userId)
    {
        User user = getUserFromDatabase(userId);

        if (loggedUsers.Users.Exists(u => u.Id == userId))
            throw new UserAlreadyExistsException();

        loggedUsers.Users.Add(user);

        if (!loggedUsers.LastUserLoginDateAndTime.ContainsKey(user.Id))
            loggedUsers.LastUserLoginDateAndTime.Add(user.Id,DateTime.Now);
        else
            loggedUsers.LastUserLoginDateAndTime[user.Id] = DateTime.Now;

        return user;
    }
}

Since RegisterUserLogin performs direct access to inner content of LoggedUsersController, it contributes to a tighter coupling from the caller to the behavior of LoggedUsers. A better approach is to isolate the behavior inside LoggedUsersController.

C#
public class LoggedUsersController
{
    private Dictionary<int, DateTime> LastUserLoginDateAndTime;
    private List Users;

    public void AddUser(User user)
    {
        if (this.Users.Exists(u => u.Id == user.Id))
            throw new UserAlreadyExistsException();

        this.Users.Add(user);
        if (!this.LastUserLoginDateAndTime.ContainsKey(user.Id))
            this.LastUserLoginDateAndTime.Add(user.Id, DateTime.Now);
        else
            this.LastUserLoginDateAndTime[user.Id] = DateTime.Now;
    }
}

public class BusinessRule
{
    private LoggedUsersController loggedUsers =
                new LoggedUsersController();

    public User RegisterUserLogin(int userId)
    {
        User user = getUserFromDatabase(userId);
        loggedUsers.AddUser(user);
    }
}

Now, the BusinessRule class is not tied to the implementation of LoggedUsersController. Instead, it is interested only in the responsibilities of its interface. It does not know details about the implementation of LoggedUsersControllers anymore, which contributes to a looser coupling. Moreover, all the logic related to the data of LoggedUsersControllers is handled closer, eliminating the inappropriate intimacy, which increases cohesion of the class.

Types of Coupling

The types are listed in order of the highest to the lowest coupling.

  • Content coupling (worst) occurs when one component depends on internal data or behavior of another component. This is the worst degree of coupling, since changes to one component will almost certainly require modification to others.
  • Common coupling occurs when modules share common data, like global variables. As we all know, globals are evil. Changing the shared resource implies changing all the modules using it.
  • Control coupling occurs when one service or module knows something about the implementation of another and passes information to control its logic.
  • Stamp coupling occurs when modules share objects and use only a part of it. Sharing more data than what was needed allows the called module to access more data than it really needs.
  • Data coupling occurs when one module or service shares data between each other. Data passed as parameter to a function call is included in this type of coupling. Although services with many parameters are a bad sign of design, well handled data coupling is preferable when compared to other forms of coupling.
  • No coupling (best) – No intersection between modules.

If two or more components need to communicate, they should exchange as little information as possible.

Cohesion

Cohesion is a measure of responsibility and focus of an application component. It is the degree to which the elements of a module belong together, which should be maximized.

We want strong-related responsibilities in a single component. Thus, we want to develop highly cohesive code.

In a highly cohesive code, all data, methods and responsibilities are kept close. Services tend to be similar in many aspects.

A simple and intuitive way to test the cohesion of a class is to check if all the data and methods that it contains have a close relationship with the class name. Considering this, you should be aware that generic class names tend to generate cohesion problems, because they can get many different responsibilities over time. In fact, classes that have a vague name might one day become god objects (an anti-pattern that defines an “all-knowing” object that contains tons of features and services with many different purposes, which dramatically compromises cohesion and coupling of components).

The Law of Demeter: Talk Only To Your Closest Friends

Image 3

Also known as Principle of Least Knowledge or just LoD, the law of Demeter governs the interconnection between components. It reinforces loose coupling and high cohesion by stating that your object-oriented entities should only be talking to their closest friends.

The Law of Demeter states that a method of a given object should only access methods and accessors belonging to:

  • The object itself
  • Parameters passed in to the method
  • Any object created within the method
  • Direct component elements of the object

Long chaining of accessors and methods is a sign of bad design.

For example:

C#
public class BusinessRule
{
    public Bid GetCarAuctionBestBid(int carId)
    {
        //... some logic here
        return bidsLogic.Bids.AllBids.GetBestBid(b => b.CarId = carId);
    }
}

Even if you need a particular information that is at the end of the chain, digging out the methods and accessors yourself is a terrible idea.

C#
public class BusinessRule
{
    public Bid GetCarAuctionBestBid(int carId)
    {
        //... some logic here
        return bidsLogic.GetBestBid(carId);
    }
}

Now you are only talking to your closest friend. “bidsLogic” resolves its properties internally and exposes services that are explicitly needed by others components. There is another thing going on here. The law of Demeter is not just about chaining. When you do not have to worry about navigating accessor and methods (i.e., when you have what you need by just calling a method or property of a nearby object), we say that you are telling the object what to do. The principle of least knowledge is closely related to the principle “Tell, don’t ask”!

Tell, Don’t Ask

The problem is not to use “get” accessor to understand a behavior of an object. The problem is to make decisions based on that. You do not want to ask the object about its inner state, make decisions about that state and then perform some dark operation. Object-oriented programming tells objects what to do.

The sample below is an example of code that asks too much. The code makes decisions about the state of the bill by adding prices contained in the list of Items assuming that the sum represents the total value of the bill.

C#
public class BusinessRule
{
    public double CalculateDinnersCost(List<Bill> bills)
    {
       return bills != null ? bills.SelectMany(b => b.Items).Sum(i => i.Price) : 0;
    }
}
public class Bill
{
    public Bill()
    {
       this.Items = new List<Item>();
    }
    public List<Item> Items { get; private set; }

    public void AddItem(Item item) {...}
    public void Remove(int itemId) {...}
}

Instead of asking that much, if the state of an object can be inferred by examining closer accessors and methods, we should consider relocating the logic inside the right object. This is the “Tell, don’t ask” principle: instead of developing procedural code, we tell the objects what to do.

C#
public class BusinessRule
{
   public double CalculateDinnersCost(List<Bill> bills)
   {
      return bills != null ? bills.Sum(b => b.CalculateTotalCost()) : 0;
   }
}
public class Bill
{
   private List<Item> items;

   public Bill()
   {
      this.items = new List<Item>();
   }

   public void AddItem(Item item) {...}
   public void Remove(int itemId) {...}

   public double CalculateTotalCost()
   {
      return this.items.Sum(i => i.Price);
   }
}

Single Responsibility Principle

The Single Responsibility Principle is straightforward:

Every class should have a single responsibility and have one, and only one, reason to change.

Imagine an entity called MessagesRegister, which is responsible for registering alerts and notifications.

This is how MessagesRegister works:

  • It reads a configuration file
  • It processes some business logic to create notifications.

This class can be changed for two reasons. First, the source of the configuration can evolve into something more elaborate over time (instead of XML, a configuration through graphical user interface). Furthermore, the actual processing rule might change.

It is a bad design decision to keep together two pieces of information that are changed for different reasons. The cohesion (i.e., focus and responsibility of the class) is reduced and the principle of separation of concerns is violated.

Putting It All Together

So… where should I put this code? While coupling measures the degree of dependence between different entities of the system, cohesion is a measure of how focused the responsibilities of a given entity are.

By following these principles, we can enumerate some major achievements in our design:

  • Maintenance is child’s play: we keep together things that need to be maintained together and things that are not directly related can be changed without affecting other components.
  • The code is readable and algorithms become more self-documenting.
  • Classes have well-defined responsibilities and code duplication is drastically reduced.

Image 4

Related Readings

Image 5

License

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


Written By
Software Developer
Canada Canada
Software architect, Machine learning engineer, full stack cloud developer, MSc in Machine Learning, Microsoft Certified Solutions Developer (MCSD).

Comments and Discussions

 
Questiontwo additional principles Pin
George Shimanovich25-Nov-14 15:43
George Shimanovich25-Nov-14 15:43 
AnswerRe: two additional principles Pin
Arthur Minduca28-Nov-14 5:26
Arthur Minduca28-Nov-14 5:26 
QuestionThis is a general principle .. Pin
--CELKO--25-Nov-14 14:15
--CELKO--25-Nov-14 14:15 
AnswerRe: This is a general principle .. Pin
Arthur Minduca28-Nov-14 5:17
Arthur Minduca28-Nov-14 5:17 
GeneralRe: This is a general principle .. Pin
--CELKO--29-Nov-14 2:37
--CELKO--29-Nov-14 2:37 
GeneralRe: This is a general principle .. Pin
Arthur Minduca30-Nov-14 2:10
Arthur Minduca30-Nov-14 2:10 
GeneralRe: This is a general principle .. Pin
Dewey17-Mar-17 6:22
Dewey17-Mar-17 6:22 
Questionimages missing Pin
BigTimber@home24-Nov-14 2:45
professionalBigTimber@home24-Nov-14 2:45 
AnswerRe: images missing Pin
Arthur Minduca24-Nov-14 4:57
Arthur Minduca24-Nov-14 4:57 
Thank you BigTimber. I am not sure about how am I loosing the links. But it still happens. Fixed again.
GeneralRe: images missing Pin
Anders Baumann24-Nov-14 5:20
Anders Baumann24-Nov-14 5:20 
GeneralRe: images missing Pin
Arthur Minduca24-Nov-14 5:35
Arthur Minduca24-Nov-14 5:35 
GeneralRe: images missing Pin
Anders Baumann24-Nov-14 9:40
Anders Baumann24-Nov-14 9:40 
GeneralRe: images missing Pin
Arthur Minduca24-Nov-14 12:47
Arthur Minduca24-Nov-14 12:47 

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.