Design principles encourage us to create more maintainable, understandable, and flexible software. Consequently, as our applications grow in size, we can reduce their complexity
A class should only have one responsibility. Furthermore, it should only have one reason to change.
In BookV1
the print method violates the SRP. For this reason the BookV2
is amended to remove the method and
BookPrinter
is implemented a separate class that is concerned only with printing.
Not only have we developed a class that relieves the Book of its printing duties, but we can also leverage our
BookPrinter
class to send our text to other media.
Classes should be open for extension, but closed for modification.
After the first implementation of Guitar
, we want to add in a second implementation, a flame pattern.
It might be tempting to just open up the Guitar
class and add a flame pattern – but who knows what errors that might
throw up in our application.
Following OCP we create SuperCoolGuitarWithFlames
by extending the Guitar class we can be sure that our existing
application won't be affected.
If class A is a subtype of class B, then we should be able to replace B with A without disrupting the behavior of our program.
Defined a Car
interface, we have implemented a MotorCar
. We want to introduce electric cars which by definition
is a car without the engine. ElectricCar
is the implementation. This a violation of Liskov substitution principle.
One possible solution would be to rework our model into interfaces that take into account the engine-less state of
our Car
Larger interfaces should be split into smaller ones. By doing so, we can ensure that implementing classes only need to be concerned about the methods that are of interest to them
We define a BearKeeper
class. As avid zookeepers, we're more than happy to wash and feed our beloved bears.
However, we're all too aware of the dangers of petting them.
Only the crazy zookeepers can do that. But with this implementation we force every zookeepers to pet bears.
We create 3 new interfaces BearCleaner
, BearFeeder
and BearPetter
.
Now, thanks to interface segregation we can implement BearCarer
and CrazyPerson
.
The principle of Dependency Inversion refers to the decoupling of software modules. This way, instead of high-level modules depending on low-level modules, both will depend on abstractions.
In Windows98Machine
class by declaring the StandardKeyboard
and Monitor
with the new keyword, we've tightly
coupled these 3 classes together
Not only does this make our Windows98Computer hard to test, but we've also lost the ability to switch out our StandardKeyboard class with a different one should the need arise. And we're stuck with our Monitor class, too.
To implement DI we:
- adding a more general Keyboard interface
- rewrite it to
Windows98MachineDI
Now our classes are decoupled and communicate through the Keyboard abstraction. If we want, we can easily switch out the type of keyboard in our machine with a different implementation of the interface. We can follow the same principle for the Monitor class.
We've decoupled the dependencies and are free to test our Windows98Machine with whichever testing framework we choose.