SOLID2-LSP AND ISP

LSP - The Liskov Substitution Principle

The Liskov substitution principle sates that sbtypes must be substitutable for their base types

Substitutability

Child classes must not:

And in general must not require calling code to know they are different from their base type

Inheritance and the IS-A relationship

Naive OOP teaches use of IS-A to describe child classes relationship to base classes

LSP suggests that IS-A should be replaced with IS-SUBSTITUTABLE-FOR

Invariants

invariants are things that have some things to do with the integrity of the model the classes represent

Example: We have two shape related classes. A rectangle class with length and width , and we derive square from rectangle

public class Rectangle
  {
      public virtual int Height { getset; }
      public virtual int Width { getset; }
  }

  public class Square : Rectangle
  {
      private int _height;
      private int _width;
      public override int Width
      {
          get
          {
              return _width;
          }

          set
          {
              _width = value;
              _height = value;
          }
      }
      public override int Height
      {
          get
          {
              return _height;
          }

          set
          {
              _height = value;
              _width = value;
          }
      }
  }

Here we have the area calculator that knows how to calculate the area of a rectangle:

public class AreaCalculator
  {
      public static int CalculateArea(Rectangle r)
      {
          return r.Height * r.Width;
      }
      public static int CalculateArea(Square s)
      {
          return s.Height * s.Height;
      }
  }

Here we have the three unit tests, the first two should pass, but the third does not because the Square class is not substitutable for the rectangle class

[TestClass]
   public class CalculateAreaShouldReturn
   {
       [TestMethod]
       public void SixFor2X3Rectangle()
       {
           var myRectangle = new Rectangle { Height = 2, Width = 3 };
           Assert.AreEqual(6, AreaCalculator.CalculateArea(myRectangle));
       }
       [TestMethod]
       public void SixFor2X5Rectangle()
       {
           var myRectangle = new Rectangle { Width = 3 };
           Assert.AreEqual(9, AreaCalculator.CalculateArea(myRectangle));
       }
       [TestMethod]
       public void TwentyFor4X5RectangleFromSuare()
       {
           Rectangle newRectangle = new Rectangle();
           newRectangle.Width = 4;
           newRectangle.Height = 5;
           Assert.AreEqual(20, AreaCalculator.CalculateArea(newRectangle));
       }
   }

The square class violates the behavior of the rectangle which simply states, when one sets the height and width of an actual rectangle this should not have any effect on one another, it should be possible to set the height in depended of the width and vise versa, our implementation of the square class has broken that expectation of clients

Another problem with this implementation: the Area calculator violates a principle called "tell do not ask". In this case it is asking the parameter rectangle for his height and his width, and the square for his height so he can square the height. The problem his is that we have behavior decoupled form state

The state of the rectangle, his height and his width, is being contained in the rectangle class, and the behavior, his area calculation is been moved to the area calculator, in this case our rectangle lacks cohesion because operations that are only depended on rectangle (calculateArea) have now been moved to this separate class which can not exists on its own, this class only works if it is able to collaborate with the rectangle

It might be worth considering a design change that pushed the responsibility of calculating the area in to the rectangle class or the square class, there are several ways to do this, you will expect to put this logic in a base class because it is common to many different types of geometric shape buy lets consider that we wont to that and we will implement this:

public abstract class Shape
{

}
public class Rectangle : Shape
{
    public virtual int Height { getset; }
    public virtual int Width { getset; }

    public  int Area()
    {
        return Height * Width;
    }
}
public class SquareShape
{
    public int  SideLengtht { getset; }
    public  int Area()
    {
        return SideLengtht * SideLengtht;
    }
}

Above, we have an abstract class "Shape" which the classes Rectangle and Square derive from and the method Area in each of them

We use these tests:

[TestClass]
    public class CalculateAreaShouldReturn
    {
        [TestMethod]
        public void SixFor2X3Rectangle()
        {
            var myRectangle = new Rectangle { Height = 2, Width = 3 };
            Assert.AreEqual(6, myRectangle.Area());
        }
        [TestMethod]
        public void NineFor3X3Square()
        {
            var MySquare = new Square { SideLengtht = 3 };
            Assert.AreEqual(9, MySquare.Area());
        }
        [TestMethod]
        public void TwentyFor4X5ShapeFromRectangleAnd9For3X3Square()
        {
            var shapes = new List<Shape>
            {
                new Rectangle { Height=4, Width=5 },
                new Square {SideLengtht=3 }
            };
            var areas = new List<int>();
            foreach (Shape shape in shapes)
            {
                if (shapes.GetType()==typeof(Rectangle))
                {
                    areas.Add(((Rectangle)shape).Area());
                }
                if (shape.GetType()==typeof(Square))
                {
                    areas.Add(((Square)shape).Area());
                }
            }
            Assert.AreEqual(20, areas[0]);
            Assert.AreEqual(9, areas[1]);
        }
    }

Above, on the last test we created a list of shapes so we can polymorphicly enumerate over a set of shapes and do something with each one in a way that did not care the particular type of shape that it was

In order to achieve this we need to investigate what the type was of each shape

This implementation works but it is not maintainable because the next time we add another shape we need to add a new class and a new if check in the test, as we add more and more classes this will get out of hand, plus, in the test section,we violate the OCP principle because the code in the foreach loop is no longer closed to modification

The Problem

LSP violation "Smells"

foreach (var emp in Employees)
            {
                if (emp is Manager)
                {

                    _printer.PrintManager(emp as Manager);
                }
                else
                {
                    _printer.PrintEmployee(emp);

                }
            }
public  abstract class Base
{
    public abstract void Method1();
    public abstract void Method2();
}
public class Child : Base
{
    public override void Method1()
    {
        throw new NotImplementedException();
    }
    public override void Method2()
    {
      //do stuff
    }
}

A Better Solution

public abstract class Shape
   {
       public abstract int Area();
   }
   public class Rectangle : Shape
   {
       public virtual int Height { getset; }
       public virtual int Width { getset; }

       public override int Area()
       {
           return Height * Width;
       }

   }
   public class SquareShape
   {
       public int  SideLengtht { getset; }
       public override  int Area()
       {
           return SideLengtht * SideLengtht;
       }
   }
   [TestClass]
   public class CalculateAreaShouldReturn
   {
       [TestMethod]
       public void SixFor2X3Rectangle()
       {
           var myRectangle = new Rectangle { Height = 2, Width = 3 };
           Assert.AreEqual(6, myRectangle.Area());
       }
       [TestMethod]
       public void NineFor3X3Square()
       {
           var MySquare = new Square { SideLengtht = 3 };
           Assert.AreEqual(9, MySquare.Area());
       }
       [TestMethod]
       public void TwentyFor4X5ShapeFromRectangle()
       {
           Shape myShape = new Rectangle() { Height = 4, Width = 5 };
           Assert.AreEqual(20, myShape.Area());
       }
       [TestMethod]
       public void TwentyFor4X5ShapeFromRectangleAnd9For3X3Square()
       {
           var shapes = new List<Shape>
           {
               new Rectangle { Height=4, Width=5 },
               new Square {SideLengtht=3 }
           };
           var areas = new List<int>();
           foreach (Shape shape in shapes)
           {
               areas.Add(shape.Area());
           }
           Assert.AreEqual(20, areas[0]);
           Assert.AreEqual(9, areas[1]);
       }
   }

By applying the above refactoring we break apart the rectangle and square classes so square is no longer a rectangle since they are not substitutable for one another and moving the behavior into shape so we van access it polymorphicly on each object itself. Now we are able to get a design that now fallows LSP and OCP because we could add additional shapes and add them to this collection and not tough the code of the calculation

When do w fix LSP?

LSP tips

Summary

ISP: The Interface Segregation Principle

The interface segregation principle states that Clients should not be forced to depend on methods they do not use

Prefer small, cohesive interfaces to "fat" interfaces

What in an interface?

Interface keyword/type

public interface IDoSomething{...}

Public interface of a class

public class SomeClass{...}

For a real example we look at an implementation of an ASP.NET abstract class called MemberShipProvider

namespace AspDotNetMvcTraning.Classes.CodesForCopyAndPase.SOLID.LSP
{
    public class CustomMemeshipProvider : MembershipProvider
    {
        public override string ApplicationName
        {
            get
            {
                throw new NotImplementedException();
            }

            set
            {
                throw new NotImplementedException();
            }
        }

        public override bool EnablePasswordReset
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override bool EnablePasswordRetrieval
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override int MinRequiredPasswordLength
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override int PasswordAttemptWindow
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override string PasswordStrengthRegularExpression
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override bool RequiresUniqueEmail
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }
    }
}

The class above has a very fat interface with lots of unimplemented functions

Interface segregation violations result in classes that depend on things they do not need, increasing coupling and reducing flexibility and maintainability < /p>

The solution here is to use a role and header type interfaces and try to use one or two functions per interface or class

Another solution is to compose a big interface from some small interfaces

ISP Smells

When do we fix ISP

ISP Tips

Summary