OO Design Principles
SOLID
- Reduces complexity: Makes design and code more understandable
- More maintainability and flexibility: Makes classes and their dependencies less vulnerable to code-breaks due to any change
Single Responsibility
A class should have one and only one reason to change - that is, a class should only have one job.
public class Report {
public void generateReport() {
// Logic to generate report
}
}
public class ReportPrinter {
public void printReport(Report report) {
// Logic to print report
}
}
What qualifies as a single responsibility can be subjective and depends on the context.
When we say single-responsibility, the class could represent an entity(User) or action(UserFactory) or functionality(UserAuthHandler).
Deciding the level of granularity can depend on below factors:
- Cohesion:
- Class methods and properties should be closely related to its main responsibility. If the properties or members of a class start to become unrelated - split it.
- If one class depends very heavily on another for its operation, it may make sense for them to share some responsibility or be part of the same cohesive group.
- Separation of Concerns: You can try to separate out technical logic (like data access), business logic and presentation logic. Single class shouldn't manage too many interactions => tight coupling.
Classes should be granular enough to ensure a clear, focused purpose, but not so granular that it becomes cumbersome.
Open-Closed Principle
Entities (classes, methods, functions etc.) should be open for extension but closed for modification.
Liskov Substitution Principle
If B is a subtype of A, then objects of type A may be replaced with objects of type B (i.e., an object of type A may be substituted with any object of a subtype B) without altering any of the desirable properties of the program (correctness, task performed, etc.)
- For instance, if the type Cat is a subtype of Animal, then an expression of type Cat should be substitutable wherever an expression of type Animal is used.
- Behavior Animal walks on four legs. Cat walks on four legs.
- Attribute Animal has two eyes. Cat has two eyes.
Interface Segregation
Clients should not be forced to depend on the interfaces they do not use. Separate out your interfaces into multiple focused-interfaces.
Dependency Inversion
⚠️ NOTE: Not the same as dependency injection or inversion of control.
- 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.
Basically, when designing an interface, your design should not depend on the implementation details. You code shouldn't be such that if you change your child class, you'd need to change your superclass or interface.
Also, when using dependencies in your code, you should use the interface, not the class itself. So that, the interface can be switched with any implementation.
Law of Demeter / Principle of Least Knowledge
Don't talk to strangers.
Statement
For all classes C, and for all methods M attached to C, all objects to which M sends a message must be:
- M’s argument objects, including the self object or
- The instance variable objects of C
(Object created by M, or by functions or methods which M calls, and objects in global variables are considered as arguments of M).
In the below example:
class C{
B b;
public void m(A arg){
}
}
method m
should only use the argument arg
, the self-reference this
and the instance member b
, in its implementation.
This means –
- Dependency Injection
In the early days of Object-Orientation, objects were supposed to “send messages” to each other. That is how they communicated. So the term “objects to which M sends a message” roughly translates to “objects used by M,” or in a more practical definition “objects on which M calls a method on”.
The Genius of Law of Demeter
- Concepts like encapsulation, cohesion, single-responsibility principle or open/closed principle are not pragmatic enough and require interpretation, which makes them subjective and dependent of people's experience and knowledge.
The genius of the Law of Demeter comes from the succinct and exact definition, which allows for a direct application when writing code, while at the same time almost automatically guaranteeing proper encapsulation, cohesion, and loose coupling. The authors of the Law managed to take these abstract concepts and distil the essence of them into a clear set of rules that are universally applicable to Object-Oriented code.
Composition Over Inheritance
aka Composite reuse Principle. #^5c3039
It is a OOP principle which encourages the usage of composition for code reuse instead of inheritance.
However, it is still a principle and Philosophy#A Principle is just in Principle.
Hence, this can be reframed better as -
If you are going to use inheritance - think again, chances are you need composition.
Trade-offs
OOP#Purpose of Inheritance | OOP#Disadvantages of Inheritance |
---|---|
OOP#Benefits of Composition | OOP#Disadvantages of Composition |
Do NOT initialise dependencies
- Do no work in a constructor
- Do not create significant dependencies directly but via arch.design.oo.patterns.gof.creational.factory or lazy initialisation.
Inversion of Control
- IoC inverts the flow of control as compared to traditional control flow.
- A software architecture with this design inverts control as compared to traditional procedural programming.
- Normally, what happens is your application code does most of the work and you might use certain libraries to add extra functionality. Or your class is dependent upon other classes to get the job done.
In case of IoC, the framework handles most of the jobs and you focus on just the specific implementation of your application.
Benefits
As mentioned, one of the benefits is you focus on just your specific logic and implementation and you let the framework handle the trivial stuff like creating an object, allocating memory etc.
It also allows us to depend more on abstraction rather than concrete implementations which in turn promotes:
- loosely coupled architecture
- flexibility
- plug-ability
Don't dig too deep in chain calls
Chain calls ko dil pe na lein.
Avoid digging to deep into collaborators while creating chain calls as it can risk exposing internals and cause other issues.
//the dog should bark
dog->getHead( ) ->getMouth( ) -> getTongue( ) ->bark( ).
👆🏽Tight Coupling and dependencies
- Hard to change/extend
- No reuse of parts possible
- Hard to test
Solution
dog->bark( ).
References
- Why SOLID principles are still the foundation for modern software architecture ^ba0273
- Michael Feathers - the deep synergy between testability and good design ^464b31
- Prefer composition over inheritance? - Stack Overflow ^5c3039
- Ryan Schachte - Dependency Injection & Inversion of Control - YouTube ^e4e407
- The Genius of Law of Demeter - dZone ^549ca1