Click here to Skip to main content
15,886,067 members
Articles / Operating Systems / Windows
Tip/Trick

High Cohesion & Low Coupling Using SOLID Principles -- Part 2

Rate me:
Please Sign up or sign in to vote.
4.94/5 (5 votes)
23 Jun 2015CPOL4 min read 18.2K   8   2
High Cohesion & Low coupling using SOLID Principles -- Part 2

Introduction

This post is a continuation from High Cohesion & Low coupling using SOLID Principles -- Part 1.

Liskov Substitution Principle [LSP]

The Liskov Substitution Principle states subtypes must be semantically substitutable for their base types.
In other words, it means that consuming any implementation of a base class should not change the correctness of the system.

Hopefully even in more simpler words, you should be able to replace any object with a sub class of that object and have all of the code still function properly.

LSP helps in loose coupling by allowing any code that uses subclass - objects does not need to know about the what & where part of these objects.

The benefits of this principle really shine when applied along with the Open/Closed principle. If a class is extended into multiple sub-classes, you can still use the same code with the main class within these new classes. This way, you prevent dependencies and increase the ability to re-use code.

Simple Example

A simple example of LSP is a square and a rectangle issue. 

Let us say you have to calculate the area of square and rectangle. For calculating the area of a rectangle, you will need a height and a width. And for square, you need only one coordinate that is length.

Mathematically thinking, all the squares are rectangle, but not all rectangles are square. Since a square “is a” rectangle, you could fall into a trap to create a rectangle as a base class for square. But what happens when you try to change the height or width of a square? Can you? No, you cannot. A square should have same length for all the four coordinates. 

So as per LSP, If you try to inherit from rectangle to create a square, you end up changing the semantics of height and width (I mean overriding the code of properties or methods) to account for this. Or in other words, to demonstrate square is always a rectangle you might end up changing the semantics of superclass or subclass and making them non substitutable -- thus violating the LSP. -- Refer square - rectangle image below.LSP -1

To solve this issue, one may use a simple IShape abstraction and each rectangle and square implement this IShape.

How LSP Helps Achieve Low Coupling and High Cohesion

An object inheriting from an abstraction must be semantically substitutable for the original abstraction. Now any code that uses this object need not care about the what part and where (this object is passing from) part to provide loose coupling. Also, a module designed in LSP principle is very easily integrated in a cohesive manner.

Another Illustration

Let's consider an implementation for the same. Let's say we have 3 classes: Super Class, Sub class (inheriting from Super Class ) and a Demo class (test harness).

LSP2

  • SuperClass contains 2 decimal fields: n1 & n2 along with a constructor and a Multiply method to calculate n1*n2.
  • SubClass inherited from SuperClass and contains a constant value to be used in Multiply method
  • Demo Class contains Go method and a test harness Main method
  • Main method first invokes GO method with a superclass object and displays the result & then exactly the same operation with SubClass object.

As of now, it is simple little inheritance example with no hint of LSP. (Refer to the image & code below.)

C#
public class SuperClass
    {
        internal decimal n1;
        internal decimal n2;

        public SuperClass(decimal num1, decimal num2)
        {
            this.n1 = num1;
            this.n2 = num2;
        }
        public virtual decimal? Multiply()
        {
            return this.n1 * this.n2;
        }
    }

public class SubClass : SuperClass
    {
        const decimal constVal = 1.1m;

        public SubClass(decimal num1, decimal num2) : base(num1, num2) { }

        public override decimal? Multiply()
        {
            try
            {
                return base.Multiply() * constVal;
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }
    }

public class Demo
    {
        public static decimal? GO(SuperClass b)
        {
            return b.Multiply();
        }

        public static void Main()
        {
            SuperClass b = new SuperClass(-5, 20);
            decimal? result = GO(b);
            Console.WriteLine(result);

            SubClass d = new SubClass(-5, 20);
            decimal? result2 = GO(d);
            Console.WriteLine(result2);

            Console.Read();
        }
    }

So far so good with a trivial example.

But what if I introduce a condition to SubClass -- Multiply method. Could a small change of a condition break the semantics & thus violate LSP? Let's check this other code snippet below:

C#
public class SubClass : SuperClass
    {
        const decimal constVal = 1.1m;

        public SubClass(decimal num1, decimal num2) : base(num1, num2) { }

        public override decimal? Multiply()
        {
            try
            {
                if (this.n1 < 0)
                    throw new InvalidOperationException("Number n1 cannot be negative");
                return base.Multiply() * constVal;
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }
    }    
    
    public static void Main()
        {
            SuperClass b = new SuperClass(-5, 20);
            decimal? result = GO(b);
            Console.WriteLine(result);

            SubClass d = new SubClass(-5, 20);
            decimal? result2 = GO(d);
            Console.WriteLine(result2);

            Console.Read();
        }

In this, the overridden method adds an additional condition to check if field n1 is negative and if so, it throws an exception. Essentially, what happens now is the same old Main method is used against this new overridden Multiply(), you would recognize that instead of printing result for SubClass it would throw an exception whereas Multiply from SuperClass would still execute properly. This additional condition required makes the base class and derived class not interchangeable anymore and therefore the Liskov Substitution Principle is violated.

But when you develop your derived class and specifically when the virtual method is redefined, you must pay attention to some situations, if they occur, involving the violation of the Liskov substitution principle. Specifically, a general rule to keep in mind is that a derived class should *not* require additional conditions or either provide fewer conditions than those required by the base class.

N.B.: This is just one of the examples of violating LSP. LSP could be violated in many other ways as well.

************************I will publish my Part-3 sometime day after tomorrow **********************

License

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


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMore info about LSP Pin
Lechuss29-Jun-15 9:27
Lechuss29-Jun-15 9:27 
AnswerRe: More info about LSP Pin
vaid_sumit29-Jun-15 21:57
vaid_sumit29-Jun-15 21:57 

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.