There should never be more than one reason for a class to change.
A responsibility for a class is a reason of change. Mixing responsabilities causes strict coupling, unability or difficulty to reuse modules, fragility (changes break other 'unrelated' parts of the system). When a class has more than one responsability, the responsabilities become coupled together, and may get in the way of each other when the requirements change. A class/software becomes fragile.
The SRP helps keep classes and methods small, maintainable, focussed, and easier to understand.
The Shape
class has two methods: draw
and area
.
draw
is related to the GUI, area
is related to calculation.
This Shape
may change for different reasons, coming from differnet "departments" (marketing,design,po,etc.).
Having unrelated responsabilities coupled together impairs the class to change, in addition the system may break for unexpected reasons.
Entities should be open for extension, but closed for modification.
Software changes during its lifetime. The question is: What's a good way to build software that is maintainable and stable against change?
A desired approach to handle change is to adding new, fresh code, not by modifying old code that is working and tested.
What does "open for extension" and "closed for modification" mean:
- open for extensions means that the module (or the behaviour) can be extended to meet the new requirements.
- closed for modification means that changes to existing modules are not allowed/desired.
But how can one achieve this principle if a module that has to be extended in its behaviour cannot be modified? Through abstraction.
In other words: a module can be closed for modification if it depends only on an abstraction, that can be extended (open for modification) through inheritance. Abstract classes, interfaces, etc help us to achieve this.
This principle is achieved through the use of interfaces, because the interface is closed for modification, a module that uses this interface implements, at a minimum, the required "contract"/methods.
This is not necessarily achieved only through inheritance, a software can also be conform to ocp if not OO.
It depends on the programmer how much encapsulation is needed/desired for a certain component, knowing about the client's needs, likely to change requirements, etc.
This principle states, that if a class X is a subtype of Y, then objects of class X can be replaced by any other subtype of Y. The important part, besides from being able to substitute these subtypes, is that the behaviour should not change. It is also called behavioral subtyping.
A typical example of LSP is the Rectangle-Square-Problem:
In practice a Square is a subtype of a Rectangle. The getters and setters for the Rectangle type are defined for height and width, thus implemented in Square. Though a square should maintain the width and height equal, so we would override the setter of a specific property (height/width) and set also the other to be a consistent square. But this is not the expected behaviour of the type Rectangle. It is not fully substitutable with a Square, since it behaves differently.
This principle states: do not depend on things you don't need. Or better: don't force your clients to depend on things the don't need. An important related concept is that interfaces have more to do with classes that use them, that with classes that implement them. This is why interfaces should be focused, specialized. An interfaces should represent a single "responsability". How detailed you want to make your interfaces is up to you.
An example could be the repository pattern:
One could create a full-fledged CRUD repository and force their clients to implement every operation. Some may only need to read from the repo, but they have to implement the other operation too. This problem could be solved with a separate ReadRepository and WriteRepository. In addition you could extend both and create the original CRUDRepository by extending both ReadRepository and WriteRepository.
High-level modules should not depend on low-level modules, both should depend upon abstractions. Abstractions should not depend on details, details should depend on abstractions.
High-level modules contain business logic, flows and make decisions, these should not depend on low-level modules. It's the high-level modules that should have the control over low-level modules, certainly not adapt to changes of them. Otherwise this limits the reuse of high-level modules, causes coupling, and other 'diseases' like rigid (due to hardcoded dependencies), fragile (changes cause unexpected things), immobile (difficult to reuse) software.
The dependency inversion is often achieved by following the OCP in OO, in procedural and FP it is achieved through other mechanisms. Other patterns I read are used are for example the Factory Pattern.
The example used to describe the concept of high/low-level modules and the DIP is a simple Copy program.
The Copy program contains the logic, business rules to do its job. Other low-level components that do the work are the FileWriter and the KeyboardReader. In the 'rigid' example these dependencies are coupled with the high-level module, and cause difficulty to change when new requirements arrive. The code slowly rots and becomes difficult to maintain as new requirements are implemented and the software gets out of hand.
It could be solved with decoupling the low-level modules that cause this rigidity, by inverting the strict dependency that points toward the Copy program by placing an interface in between.
CopyProgram --> Writer/ReaderInterface <-- FileWriter,VideoWriter,KeyboardReader,FileReader etc.
To understand DIP better I used these resources:
- http://c2.com/cgi/wiki?DependencyInversionPrinciple
- http://programmers.stackexchange.com/questions/220765/open-close-principle-ocp-vs-dependency-inversion-principle-dip
- http://www.oodesign.com/dependency-inversion-principle.html
:wq