Conclusion – Architectural Principles
The key idea of the LSP is that the consumer of a supertype should remain unaware of whether it’s interacting with an instance of a supertype or an instance of a subtype.We could also name this principle the backward-compatibility principle because everything that worked in a way before must still work at least the same after the substitution, which is why this principle is essential.Once again, this is only a principle, not a law. You can also see a violation of the LSP as a code smell. From there, analyze whether you have a design problem and its impact. Use your analytical skills on a case-by-case basis and conclude whether or not it would be acceptable to break the LSP in that specific case. Sometimes you want to change the program’s behavior and break the LSP, but beware that you might break certain execution paths you did not account for and introduce defects.The more we progress, the more we move away from inheritance, and the less we need to worry about this principle. However, if you use inheritance and want to ensure your subtypes don’t break the program: apply the LSP, and you will be rewarded by improving your chances of producing defect-free, backward-compatible changes.Let’s look at the ISP next.
Interface segregation principle (ISP)
Let’s start with another famous quote by Robert C. Martin:
“Many client-specific interfaces are better than one general-purpose interface.”
What does that mean? It means the following:
- You should create interfaces.
- You should value small interfaces more.
- You should not create multipurpose interfaces.
You can see a multipurpose interface as “an interface to rule them all” or a God class, introduced in Chapter 1, Introduction.
An interface could refer to a class interface (the public members of a class) or a C# interface. We focus on C# interfaces in the book, as we use them extensively. Moreover, C# interfaces are very powerful.Speaking of interfaces, let’s quickly look at them before digging into some code.
Interfaces are among the most valuable tools in the C# toolbox for creating flexible and maintainable software. It can be tough to understand and grasp the power of interfaces at first, especially from an explanation, so don’t worry if you don’t; you will see plenty in action throughout the book.
Next are some more details that overview interfaces:
- The role of an interface is to define a cohesive contract (public methods, properties, and events). In its theoretical form, an interface contains no code; it is only a contract. In practice, since C# 8, we can create default implementation in interfaces, which could be helpful to limit breaking changes in a library (such as adding a method to an interface without breaking any class implementing that interface).
- An interface should be small (ISP), and its members should align toward a common goal (cohesion) and share a single responsibility (SRP).
- In C#, a class can implement multiple interfaces, exposing multiples of those public contracts or, more accurately, be any and all of them. By leveraging polymorphism, we can consume a class as if it was any of the interfaces it implements or its supertype if it inherits another class.
A class does not inherit from an interface; it implements an interface. However, an interface can inherit from another interface.
Let’s explore the ISP example now that we refreshed our memory.