S – SRP – Single Responsibility Principle
O – OCP – Open Closed Principal
L – LSP – Liskov Substitution Principle
I – ISP – Interface Segregation Principle
D – DIP – Dependency Inversion Principle
Single Responsibility Principle
The single responsibility principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. The term was introduced by Robert C. Martin in an article by the same name as part of his Principles of Object Oriented Design, made popular by his book Agile Software Development: Principles, Patterns, and Practices. Martin described it as being based on the principle of cohesion, as described by Tom DeMarco in his book Structured Analysis and Systems Specification. Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to change. As an example, consider a module that compiles and prints a report. Such a module can be changed for two reasons. First, the content of the report can change. Second, the format of the report can change. These two things change for very different causes; one substantive, and one cosmetic. The single responsibility principle says that these two aspects of the problem are really two separate responsibilities, and should therefore be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times. The reason it is important to keep a class focused on a single concern is that it makes the class more robust. Continuing with the foregoing example, if there is a change to the report compilation process, there is greater danger that the printing code will break if it is part of the same class.
The open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behavior to be modified without altering its source code. This is especially valuable in a production environment, where changes to source code may necessitate code reviews, unit tests, and other such procedures to qualify it for use in a product: Code obeying the principle doesn’t change when it is extended, and therefore needs no such effort. Dr. Bertrand Meyer is generally credited as having originated the term Open/Closed Principle, which appeared in his 1988 book Object Oriented Software Construction. The idea was that once completed, the implementation of a class could only be modified to correct errors; new or changed features would require that a different class be created. That class could reuse coding from the original class through inheritance. The derived subclass might or might not have the same interface as the original class. Meyer’s definition advocates implementation inheritance. Implementation can be reused through inheritance but interface specifications need not be. The existing implementation is closed to modifications, and new implementations need not implement the existing interface. During the 1990s, the Open/Closed Principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other. In contrast to Meyer’s usage, this definition advocates inheritance from abstract base classes. Interface specifications can be reused through inheritance but implementation need not be. The existing interface is closed to modifications and new implementations must, at a minimum, implement that interface. Robert C. Martin’s 1996 article “The Open-Closed Principle” was one of the seminal writings to take this approach.
Liskov Substitution Principle
The Liskov substitution principle (LSP) is a particular definition of a sub typing relation, called (strong) behavioral sub typing, that was initially introduced by Barbara Liskov in a 1987 conference keynote address entitled Data abstraction and hierarchy. It is a semantic rather than merely syntactic relation because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Liskov and Jeannette Wing formulated the principle succinctly in a 1994 paper as follows:
Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.
A typical example that violates LSP is a Square class that derives from a Rectangle class, assuming getter and setter methods exist for both width and height. The Square class always assumes that the width is equal with the height. If a Square object is used in a context where a Rectangle is expected, unexpected behavior may occur because the dimensions of a Square cannot (or rather should not) be modified independently. This problem cannot be easily fixed: if we modify the setter methods in the Square class so that they preserve the Square invariant (i.e. keep the dimensions equal), then these methods will not obey the strongest post conditions for the Rectangle setters, which state that dimensions can be modified independently. Violations of LSP, like this one, may or may not be a problem in practice, depending on the post-conditions (or invariants) that are actually expected by the code that uses classes violating LSP. Mutability is a key issue here, if Square and Rectangle only had getter methods, i.e. they were immutable objects, and then no violation of LSP could occur.
Interface Segregation Principle
The Interface Segregation Principle is a software development principle used for clean development and is intended to help a developer from making his software impossible to change. If followed, the ISP will help a system stay decoupled and thus easier to refactor, change, and redeploy. The ISP says that once an interface has gotten too ‘fat’ it needs to be split into smaller and more specific interfaces so that any clients of the interface will only know about the methods that pertain to them. In a nutshell, no client should be forced to depend on methods it does not use. The ISP was first used and formulated by Robert C. Martin when he was doing some consulting for Xerox. Xerox had created a new printer system which could perform a variety of tasks such as stapling a set of printed papers, faxing, and so forth. The software for this system was created from the ground up and performed its tasks successfully, but as the software grew it became harder and harder to change. Each time a change, even the smallest of changes, was made it could take nearly an hour to recompile and redeploy. This was making it near impossible to continue development, so they hired Robert to help them out. The issue with their code was that there was one main Job class that was used by almost all of the tasks. Anytime a print job or a stapling had to be done, a call was made to some method in the Job class. This meant that the Job class was getting huge or ‘fat’, full of tons of different methods which were specific to a variety of different clients. This meant that a staple job would know about all the methods of the print job, even though the staple class had no use for them. The clients were being forced to depend on methods they did not use. When a developer had to change a small detail about a print job, every one of the classes that used the Job class would have to be recompiled. The solution suggested by Robert has now become the Interface Segregation Principle. He suggested that they add a layer of interfaces to sit between the Job class and all of its clients. Using the properties of the Dependency Inversion Principle, all of the dependencies could be reversed. Instead of having just one ‘fat’ Job class that all the tasks used, there would be a Staple Job interface or a Print Job interface that would be used by the Staple class or Print class, respectively, and would call methods of the Job class. Ergo, there was an interface created for each job, and the Job class would inherit from all of these interfaces. This segregation of interfaces vastly decoupled the software allowing the clients like the Staple class to only depend on the methods it cared about. Thus if a developer made a change to the Print Job, the Staple Job would be unaffected and have no need to recompile.
Dependency Inversion Principle
The Dependency Inversion Principle refers to a specific form of decoupling where conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are inverted (e.g. reversed) for the purpose of rendering high-level modules independent of the low-level module implementation details. The principle states:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
In conventional application architecture, lower-level components are designed to be consumed by higher-level components which enable increasingly complex systems to be built. In this composition, higher-level components depend directly upon lower-level components to achieve some task. This dependency upon lower-level components limits the reuse opportunities of the higher-level components. The goal of the dependency inversion principle is to decouple high-level components from low-level components such that reuse with different low-level component implementations becomes possible. This is facilitated by the separation of high-level components and low-level components into separate packages/libraries, where interfaces defining the behavior/services required by the high-level component are owned by, and exist within the high-level component’s package. The implementation of the high-level component’s interface by the low level component requires that the low-level component package depend upon the high-level component for compilation, thus inverting the conventional dependency relationship. Various patterns such as Plug-in, Service Locator or Dependency Injection are then employed to facilitate the run-time provisioning of the chosen low-level component implementation to the high-level component. Applying the dependency inversion principle can also be seen as applying the Adapter pattern, i.e. the high-level class defines its own adapter interface which is the abstraction that the high-level class depends on. The adaptee implementation also depends on the adapter interface abstraction (of course, since it implements its interface) while it can be implemented by using code from within its own low-level module. The high-level has no dependency to the low-level module since it only uses the low-level indirectly through the adapter interface by invoking polymorphic methods to the interface which are implemented by the adaptee and its low-level module.