The Liskov substitution principle sates that sbtypes must be substitutable for their base types
Child classes must not:
And in general must not require calling code to know they are different from their base type
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 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 { get; set; } public virtual int Width { get; set; } } 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 { get; set; } public virtual int Width { get; set; } public int Area() { return Height * Width; } } public class Square: Shape { public int SideLengtht { get; set; } 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
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 } }
public abstract class Shape { public abstract int Area(); } public class Rectangle : Shape { public virtual int Height { get; set; } public virtual int Width { get; set; } public override int Area() { return Height * Width; } } public class Square: Shape { public int SideLengtht { get; set; } 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
Do not interrogate objects for their internals - move behavior to the object
Tell the object what you want it to do
Given two classes that share a lot of behavior but are not substitutable
Crate a third class that both can derive from
Conformance to LSP allows for proper use of polymorphism and produces more maintainable code
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
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
throw new NotIMplementedException
It is also a violation of Liskov substitution Principle