SOLID principles
Before even beginning to introduce all the essential OOP principles and patterns, keep in mind that:
- Focus only on KISS ("Keep it simple and stupid") and OOP Principles first, then observe your code to see which pattern it might need for a code quality improvement -> apply patterns
1. Single Responsibility
Ask yourself "what job should this class do?" before starting to code. Use one word or one sentence to describe the only responsibility of this class.The class should have high cohesion, which means its contents have strong relationships to each other, and they would be changed together and stay together.
2. Open/Closed
A class should be open for Extension, but closed for Modification, so that minimum modification is needed on existing code when implementing new functionalities.
3. Liskov Substitution
If class a is a sub type of class b, then replacing b with a should not disrupt the function of the program.
4. Interface Segregation
Interfaces should only have one responsibility and only contains methods necessary for all class/interface that might implement it. For example, the interface car should not have turnOnEngine() method, because not every class/interface that implements car would even have an engine.
5. Dependency Inversion
Why is it called "inversion"?
Because in traditional software architecture, high-level entities are built upon low-level ones, so this dependency on low-level modules make it harder to reuse the code.
Therefore, we "invert" the dependency by letting high-level modules to depend on modules with even higher-level of abstractions (for example, an interface in Java)
How to?
"Entities(include both high and low-level modules) must depend on abstractions, not on concretions."
"Abstractions should not depend on details. Details should depend on abstractions."
Example:
public class Computer{ private Monitor monitor; private Keyboard keyboard; public Computer(Monitor m, Keyboard k){ monitor = m; keyboard = k; } public void printToMonitor() { //Do sth } }
The corresponding class diagram:
To achieve DI, we can introduce an abstraction to model the interaction between high-level (Computer) and low-level (Monitor and Keyboard) modules.
"Entities (include both high-level ones, Computer, and low-level ones, Monitor and Keyboard) must depend on abstractions (IKeyboard and IMonitor), not on concretions."
Let's review the most crucial concept for dependency inversion again:
"Abstractions should not depend on details. Details should depend on abstractions."
GRASP pattern (General Responsibility Assignment Software Patterns)
Proposed by Craig Larman in 1997, which contains 9 OOP design principles. This article will introduce only 6 out of them.
Responsibility can be carried out by a single object OR a group or objects, and GRASP helps us to assign the responsibilities to class/object.
- Information expert
Rule of thumb: Assign the responsibility to a class that has the most information to fulfill that responsibility.
- Creator
Choose Class b to be the creator of class a (having the responsibility of creating an instance of class a) if one or more of following is true:
- Class b aggregates or contains objects of class a (preferred!)
- Class b records instances of A objects
- Class b closely uses objects of class a
- Class b has the data needed to initialize class a when it is created
A class is coupled with its creator, therefore we should choose the creator properly to prevent establishing unnecessary coupling.
see also: Factory Pattern ( I would introduce design patterns in a separate article.)
- Controller
The responsibility of receiving and handling system events should be assign to classes with following characteristics:
- When the class represents the overall system or subsystem.
- When the class represents a use case scenario within which the system event occurs
- Indirection
Consider assigning responsibility to build loose coupling by using "wrapper" modules.
There are three ways of implementing indirection.
- Behavioral extension: Add a module between client and target modules to provide loose coupling. See Also: Proxy and Decorator patterns.
- Interface modification: Changing a target interface to meet the clients needs. See also Adapter and Mediator patterns.
- Complexity encapsulation: Move the complex implementations to other layers. See also Facade pattern.
- Low coupling
Assign a responsibility so that coupling remains low. That means a class should have minimal dependency on other classes, so it wouldn't be easily affected by changes in other classes and is easier to understand, reuse and test.
Class A and B(class or interface) are coupled when:
- Class A is an attribute in B
- Class A call a method in B
- If A is a return value or parameter of a method in B
- A extends & implements B
- High cohesion
A class having low cohesion is undesirable, because it either contains unrelated information or methods (do too much work). This is actually a paraphrase for "Single Responsibility Principle".
References:
http://sharif.edu/~ramsin/index_files/pselecture6.pdf
https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Creator
Composition over Inheritance
Take Java for example, we prefer defining new interfaces and implementations of it instead of defining classes and extending them.


No comments:
Post a Comment