SOLID Principles
SOLID Principles
- We should always aim for high cohesion within the component (in this case class).
- Coupling is the level of inter dependency between various software components.
Tight coupling is bad in software.
- In this example, student class is tightly coupled with the database layer we use at the
backend. That is bad because now we use MySQL, but at some point we might be
interested in using other database for example MongoDB, and student class would
have to be changed. It should idealy deal with only basic student related functionalities
(id, date of birth...).
- We should move database related code to the new repository class, and then refer this
method from inside the Student class. By that we removed tight coupling, and made it
loose.
- Every software component should have one and only one reason to change. The first
Student class has at least three reasons to change (a change in the student id format, a
change in the student name format and a change in the database backend). More
reasons to change – more changes – more bugs – more money needed to fix them and
maintain software. After separating the database code into new class, there are only
two reasons to change left for the Student class (a change in the student id format and
a change in the student name format). If the reasons are closely related to each other as
those two are, we can combine them into one reason (changes to student profile).
- Example – When we buy the Wii console we get console itself and a basic remote
controller. We can also buy the extensions like Wii gun of Wii steering wheel, and use
them by just putting the basic remote controller in slot inside of them. By adding
additional features to Wii console, we did not do any visible change to Wii console or
basic remote controller (Wii does not want their customers to open the console itself in
order to add features). So it is designed to be closed for modification, but open for
extension.
- Closed for modification - New features getting added to the software component
should not have to modify existing code.
- Open for extension – A software component should be extendable to add a new
feature or to add a new behaviour to it.
- Example – Insurance company expanding their services with different types of
insurance.
- Benefits – ease of adding new features, leads to minimal cost of developing and
testing software
- Do not follow the OCP blindly – you will end up with a huge number of classes that
can complicate your overall design (decision should be subjective).
- If we apply LSP here it fails – we cannot use an Ostrich object in all the places where
we use Bird object. In case we do, and someone calls the fly method on the Bird
object, program will fail.
- LSP requires a test standard that is more strict than Is-A test. If it looks like a duck and
quacks like a duck but it needs batteries, you probably have the wrong abstraction!
- Example with car:
- In the third iteration program will not work correctly. How we fix this?
- Solution 1 – Break the hierarchy if it fails the substitution test. Racing car should no
longer extends Car. We come out with mutual parent to both – Vehicle (very generic
class).
- This design is against LSP. We should be able to deal with all the objects as Product
object itself instead of type casting to In-House Product for some of them.
- Solution 2 – Tell, dont ask. We are telling the Utils class, it does not need to ask
anything.
- Example – There is an office with a lot of printers, scanners and fax machines. We are
asked to represent these devices using object-oriented concepts in code, so we have to
design some interfaces to ensure certain level of uniformity among these devices. One
of them is multifuntional machine that is a printer, scanner and fax at the same time.
Looks like a good starting point to design common interface based on this all-in-one
device:
- Other devices should also implement this interface and override all its methods. There
is another device which is printer and scanner at the same time, and another one which
is only the printer, so methods that are related to the missing functionalities would stay
blank.
- Unimplemented methods always indicating poor design, and it is against ISP. But why
is this bad? If we have employee portal application, and one programer wants to send
a fax, he sees the fax method on our Cannon device class and invokes it without
checking it (he does not have to). Code will break because method is empty.
- Solution – Split the big interface into smaller interfaces.
- If we feel that there are some methods that are common between printer, scanner and
fax, we can have a parent interface with them and IPrint, IScan and IFax will extend it.
- How to identify violations of ISP? Fat interfaces, interfaces with low cohesion and
empty method implementations.