3. Liskov Substitution Principle: “Derived classes must be substitutable for their base classes. Objects should be replaceable with instances of their subtypes without altering the correctness of that program.” LSP is all about well designed inheritance.
- An overridden method of a subclass needs to accept the same input parameter values as the method of the superclass. That means we can implement less restrictive validation rules, but we are not allowed to enforce stricter ones in our subclass. Otherwise, any code that calls this method on an object of the superclass might cause an exception, if it gets called with an object of the subclass.
- Similar rules apply to the return value of the method. The return value of a method of the subclass needs to comply with the same rules as the return value of the method of the superclass. We can only decide to apply even stricter rules by returning a specific subclass of the defined return value, or by returning a subset of the valid return values of the superclass.
- No new exceptions can be thrown by the subtype unless they are part of the existing exception hierarchy.
- We should also ensure that Clients should not know which specific subtype they are calling, nor should they need to know that. The client should behave the same regardless of the subtype instance that it is given.
- New derived classes just extend without replacing the functionality of old classes.
One example of LSP can be found here .
There are situations where we can not apply LSP or subclassing. 3D Board game example of Head First’s ‘Object Oriented Analysis and Design’ book is good example of such case. When subclassing cannot be used, we have several other options to solve the problem:
- Delegation: When we handover the responsibility of a particular task to another class or method (Using concrete class).
- Composition: Allows us to use behavior from a family of other classes and to change that behavior at runtime (Using interface or abstraction). In composition, the object composed of the other behaviors owns those behaviors. When the object is destroyed, so are all of its behavior. The behaviors do not exist outside of the composition itself.
- Aggregation: When one class is used as part of another class, but still exists outside of that other class. We we want the benefits of composition, but we are using behavior from an object that exist outside of our project, we use aggregation.
4. Interface Segregation Principle: “Clients should not be forced to depend upon interfaces that they do not use.”
Instead of one fat interface, many small interfaces are preferred based on groups of methods with each one serving one sub-module.
Similar to the Single Responsibility Principle, the goal of the Interface Segregation Principle is to reduce the side effects and frequency of required changes by splitting the software into multiple, independent parts.
One example of ISP can be found here .
5. Dependency Inversion Principle: “Depend on abstractions, not on concretions.”
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
- An important detail of this definition is, that high-level and low-level modules depend on the abstraction. the high-level module depends on the abstraction, and the low-level depends on the same abstraction.
One example of DIP can be found here .
References: