Open Closed Principle
Continuing from where we left off in the Single Responsibility Principle Article, we'll be discussing the open closed principle using the same code examples to make further improvements.
The Open Closed Principle, is the second SOLID Principle for OOP.
For those new to SOLID, S.O.L.I.D Stands for:
- Single Responsibility Principle
- Open Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
This article is the second of a 5 post series that will cover the SOLID Principles.
Objects or entities should be open for extension, but closed for modification.
Continuing with our Garden example we are able to do the calculating of the total quantity in our Garden class because we’re assuming the classes that were passed to us were plant classes, and the only difference is root vegetables coming in bunches versus not. Here we have an if else block.
if($plant->type==“root”){
$quantity = $quantity + $plant->getPerBunch() * $plant->getCount();
}else {
$quantity = $quantity + $plant->getCount();
}
However, what if we wanted to have something like a strawberry bush in the garden? Or Raspberry? Or beans? We would have to continue adding else if blocks to the garden quantity method.
This violates the Open Closed Principle because we’re making modifications to our Garden class to extend it. So instead we can make some adjustments to our Plant class by adding a sum method.
public function sum(){
return $this->perBunch * $this->count;
}
This makes it easier for our Garden class to calculate the quantity now. We can remove the if statements in our Quantity method, and replace it with the following. Now it doesn't matter what kind of plant it is, we can always grab the sum, and the plant class is responsible for figuring out if it needs to do special calculations based on if it's a root vegetable or not.
$quantity = $quantity + $plant->sum();
However, we do have a concern. What if we are passed a class that doesn't have a sum method? Technically it could still be a Plant Class, and just have the sum class missing. Well, here is where interfaces or sometimes referred to as contracts come in handy.
Here is an example of what our Plant Interface might look like:
Interface PlantInterface { public function sum(); }
Now on our Plant classes we have to Implement the interface.
class Carrot Implements PlantInterface { }
This implementation will throw an error if the Carrot class does not have the Sum method. Any class that implements the PlantInterface
Interface MUST have the sum()
method.
Now jumping back to our Garden Class, and Quantity Method we can check to make sure a plant is really a plant.
if(is_a($plant, 'PlantInterface'))
{
$quantity = $quantity + $plant->sum();
continue;
}
throw new GardenQuantityInvalidPlantException;
So we first check to make sure the plant object is a class that implements our Plant Interface, if it is, we add it's sum to our quantity variable. If it's not we throw an exception, that this plant is not a Plant!
Now with this setup, we can easily extend our Garden's Plant support with out having to further modify our Garden class. We can now easily add any type of plant we want to our garden as long as it implements our PlantInterface
.
This meets the requirements of being open for extension, but closed for modification.