SOLID Principles
SOLID Principles
Example:
The following example is a TypeScript class that defines a Person, this class
should not include email validation because that is not related with a
person behaviour:
class Person {
public name : string;
public surname : string;
public email : string;
constructor(name : string, surname : string, email : string) {
this.surname = surname;
this.name = name;
If (this.validateEmail(email)) {
this.email = email;
} else {
throw new Error("Invalid email!");
}
}
validateEmail(email : string) {
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]
{2})?)$/i;
return re.test(email);
}
greet() {
alert("Hi!");
}
}
We can improve the class above by removing the responsibility of email
validation from the Person class and creating a new Email class that will
have that responsibility:
class Email {
public email : string;
constructor(email : string){
if(this.validateEmail(email)) {
this.email = email;
} else {
throw new Error("Invalid email!");
}
}
validateEmail(email : string) {
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]
{2})?)$/i;
return re.test(email);
}
}
class Person {
this.email = email;
this.name = name;
this.surname = surname;
greet() {
alert("Hi!");
Making sure that a class has a single responsibility makes it per default
also easier to see what it does and how you can extend/improve it.
Open Closed Design Principle
Example:
Let’s consider an Animal parent class.
Now let’s consider the Cat and Dog classes which extend Animal.
Now, wherever in our code we were using Animal class object we must be
able to replace it with the Dog or Cat without exploding our code. What do
we mean here is the child class should not implement code such that if it
is replaced by the parent class then the application will stop running. For
ex. if the following class is replaced by Animal then our app will crash.
Dependency Injection
Before we can use methods of other classes, we first need to create the
object of that class (i.e. class A needs to create an instance of class B). So,
transferring the task of creating the object to someone else and directly
using the dependency is called dependency injection. It also represents
“D” on the SOLID acronym.
Example 1:
Any application is composed of many objects that collaborate with each
other to perform some useful stuff. Traditionally each object is responsible
for obtaining its own references to the dependent objects (dependencies)
it collaborate with. This leads to highly coupled classes and hard-to-test
code.
class Car {
//The rest
}
Here, the Car object is responsible for creating the dependent objects.
What if we want to change the type of its dependent object - say Wheel -
after the initial NepaliRubberWheel() punctures? We need to recreate the
Car object with its new dependency say ChineseRubberWheel(), but only
the Car manufacturer can do that.
class Car {
this.wh = wh;
this.bt = bt;
this.wh = wh;
Example 2:
Let's try a simple example with Car and Engine classes, any car needs an
engine to go anywhere, at least for now. So below how code will look
without dependency injection.
public Car() {
engine.Start();
The issue with this code is that we tightly coupled to GasEngine and if we
decide to change it to ElectricityEngine then we will need to rewrite Car
class. And the bigger the application the more issues and headaches we
will have to add and use new types of engine.
In other words with this approach is that our high level Car class is
dependent on the lower level GasEngine class which violates Dependency
Inversion Principle(DIP) from SOLID. DIP suggests that we should depend
on abstractions, not concrete classes. So to satisfy this we introduce
IEngine interface and rewrite code like below:
void Start();
Console.WriteLine("I am electrocar");
_engine = engine;
_engine.Start();
Now our Car class is dependent on only the IEngine interface, not a
specific implementation of engine. Now, the only trick is how do we create
an instance of the Car and give it an actual concrete Engine class like
GasEngine or ElectricityEngine. That's where Dependency Injection
comes in.
gasCar.Run();
electroCar.Run();
Here we basically inject(pass) our dependency(Engine instance) to the Car
constructor. So now our classes have loose coupling between objects and
their dependencies, and we can easily add new types of engines without
changing the Car class.
The main benefit of the Dependency Injection is that classes are more
loosely coupled, because they do not have hard-coded dependencies. This
follows the Dependency Inversion Principle, which was mentioned above.
Instead of referencing specific implementations, classes request
abstractions (usually interfaces) which are provided to them when the
class is constructed.
There are two general ways to reuse the code you have already written,
Inheritance and Composition, both have their own advantages and
disadvantages, but, in general, you should always favor composition over
inheritance, if possible.
Inheritance
This encourages the use of classes. Inheritance is one of the three tenets of
OO design (inheritance, polymorphism, encapsulation).
class Person {
String Title;
String Name;
Int Age;
This is inheritance at work. The Employee "is a" Person or inherits from
Person. All inheritance relationships are "is-a" relationships. Employee also
shadows the Title property from Person, meaning Employee.Title will
return the Title for the Employee not the Person.
Composition
class Person {
String Title;
String Name;
Int Age;
this.Title = title;
this.Name = name;
this.Age = age;
class Employee {
Int Salary;
this.person = p;
this.Salary = salary;
Composition is typically "has a" or "uses a" relationship. Here the Employee
class has a Person. It does not inherit from Person but instead gets the
Person object passed to it, which is why it "has a" Person.
Composition over Inheritance
Now say you want to create a Manager type so you end up with:
This example will work fine, however, what if Person and Employee both
declared Title? Should Manager.Title return "Manager of Operations" or
"Mr."? Under composition this ambiguity is better handled:
Class Manager {
this.Title = e.Title;