SOLID-SRP and OCP
I have been trying to improve on my software architecture and adopt some best practices, one of them is SOLID principles
Here are some points that i have gathered:
encapsulation + SOLID = object oriented design principles
The single responsibility(SRP) principle
There should never be more than one reason for a class change
Cohesion and Coupling
- Cohesion: how strongly-related and focused are the various responsibilities of a module
- Coupling: the degree to which each program module relies on each one of the other modules
Strive for low coupling and high cohesion
Responsibilities Are Axes Of Changes
- Requirements changes typically map to responsibilities
- More responsibilities == more likelihood of change
- Having multiple responsibilities within a class couples together these responsibilities
- The more classes change affects, the more likely the change will introduce errors
Lets see an example of a class diagram of a store that can receive payment on-line,cash and credit card
The problem
- Cash transaction do not need credit card processing
-
Point of sale transactions do not need inventory reservation
- Store inventory is updated separately in our system
-
Point of sale transactions do not need email notifications
- The customer does not provide an email
- The customer known immediately that the order was a success
- Any change to notifications, credit card processing, or inventory management will affect order as well as the web and point of sale implementations of order
The result after the refactoring:
What is responsibility
- a reason to change
- A difference in usage scenarios form the client's perspective
- Multiple small interfaces (follow ISP) can help to achieve SRP
Summary
- Follow SRP leads to lower coupling and higher cohesion
- Many small classes with distinct responsibilities result in a more flexible design
The open/closed principle
The open closed principle states that software entities (classes,modules,functions, etc.) should be open for extension , but closed for modification
Open to extension- New behavior can be added in the future
Closed to modification- Changes to source or binary code are not required
Changes behavior without changing code?
- Relay on abstraction
- No limit to variety of implementations of each abstraction
-
In .net, abstractions include:
- Interfaces
- Abstract base classes
In procedural code, some level of OCP can be achieved via parameters
The Problems with no OCP
- Each change to code can introduce bugs and requires re tasting
- We want to avoid introducing changes that cascade through many modules in our applications
-
Writing new classes is less likely to introduce problems
- Nothing depends on new classes(yet)
- New classes have no legacy coupling to make them hard to design or test
Three Approaches to Achieve OCP
-
Parameters (Procedural Programming)
- Allow client to control behavior specifics via parameter
- Combined with delegates/lamabda, can be very powerful approach
-
Inheritance / Template method pattern
- Child types override behavior of base class (or interface)
-
Composition / Strategy pattern
-
Client code depends on abstraction
- Proves a "plug in" model
- Implementations utilize inheritance; Client utilizes composition
Lots of time we have code with lots of conditions, and we know that some were along the way we will add more conditions to the code
Lets say we have the function 'ComputeSomething' that calculates some thing according to some condition, we know that in
the future we will have more conditions
The code can look like this:
public double ComputeSomething(SomeClass someValue)
{
if (SomeClass.has("Condition1"))
{
return 1;
}
else if (SomeClass.has("Condition1"))
{
return 2;
}
else if (SomeClass.has("Condition3"))
{
return 3;
}
return 4;
}
}
A nice solution to this is to use the strategy pattern(Depend on abstraction pass in implementation)
Now we only need to add new "ICondition" class and add it to the list, we separated the implementation of the calculation and the condition matching to another class:
public class BetterImplementation
{
List<ICondition> _conditionList;
public BetterImplementation()
{
_conditionList = new List<ICondition>();
_conditionList.Add(new condition1());
_conditionList.Add(new condition2());
_conditionList.Add(new condition3());
}
public double ComputeSomething(SomeClass someValue)
{
return _conditionList.First(e => e.IsConditionTrue(someValue)).Calculate(someValue);
}
}
internal interface ICondition
{
bool IsConditionTrue(SomeClass someClass);
double Calculate(SomeClass someClass);
}
When do we apply OCP?
-
Experience tells you
- If you know from your own experience in the problem domain that a particular class of change is likely to recur, you can apply OCP up front in your design
-
Otherwise - "Fool me ones, shame on you, fool me twice, shame on me"
- Don't apply OCP at first
- If the module changes once, accept it
- If it changes a second time, redactor to achieve OCP
-
Remember TANSTAAFL
- There Ain't no such thing as a free lunch
- OCP adds complexity to design
- No design can be closed against all changes
Summary
- Conformance to OCP yield flexibility, re usability, and maintainability
- Know which changes to guard against, and resist premature abstraction