Introduction
SOLID Design Principles is an acronym for Five Design Principles.
- Single Responsibility Principle.(SRP)
- Interface Segregation Principle.(ISP)
- Open Closed Principle.(OCP)
- Liskov Substitution Principle.(LSP)
- Dependency Inversion Principle.(DIP)
Single Responsibility Principle
According to the SRP every class or module has responsibility over a single part of the functionality provided by the software. It should have only one reason to change and that is if the single piece of responsibility needs a change.
- public interface IUser
- {
- void AddUser();
- void RemoveUser();
- void UpdateUser();
- void Logger();
- void Message();
- }
If we have a deep look into the above methods we could clearly discover that for IUser it does not make sense for methods like Log() and Message() to be a part of it. So, we will be breaking it down into separate interfaces.
- public interface IUser
- {
- void AddUser();
- void RemoveUser();
- void UpdateUser();
- }
- public interface ILog
- {
- void Logger();
- }
- public interface IMessage
- {
- void Message();
- }
From here we could say all three interfaces are performing their own individual responsibilities. Now we will Use Dependency injection to implement the following code.
- public class User : IUser
- {
- public void AddUser()
- {
- Console.WriteLine("Added User");
- }
- public void RemoveUser()
- {
- Console.WriteLine("Removed User");
- }
- public void UpdateUser()
- {
- Console.WriteLine("User Updated");
- }
- }
-
- public class Log : ILog
- {
- public void Logger()
- {
- Console.WriteLine("Logged Error");
- }
- }
-
- public class Msg : IMessage
- {
- public void Message()
- {
- Console.WriteLine("Messaged Sent");
- }
- }
- class Class_DI
- {
- private readonly IUser _user;
- private readonly ILog _log;
- private readonly IMessage _msg;
- public Class_DI(IUser user, ILog log, IMessage msg)
- {
- this._user = user;
- this._log = log;
- this._msg = msg;
- }
- public void User()
- {
- this._user.AddUser();
- this._user.RemoveUser();
- this._user.UpdateUser();
- }
- public void Log()
- {
- this._log.Logger();
- }
- public void Msg()
- {
- this._msg.Message();
- }
- }
-
- class Example1
- {
- public static void Main()
- {
- Class_DI di = new Class_DI(new User(), new Log(), new Msg());
- di.User();
- di.Log();
- di.Msg();
- Console.ReadLine();
- }
- }
-
- ************End of Single Responsibility Principle*********
Interface Segregation Principle
ISP states that no client should be forced to depend on methods it does not use. Rather than having one fat interface it should split into many smaller and relevant interfaces so that clients will only have to know about the methods that are relevant to them .
Note
We already covered ISP with the previous example of SRP .
- public interface IUser {
- void AddUser();
- void RemoveUser();
- void UpdateUser();
- void Logger();
- void Message();
- }
Big fat interfaces split into relevant interfaces for the client.
- public interface IUser {
- void AddUser();
- void RemoveUser();
- void UpdateUser();
- }
- public interface ILog {
- void Logger();
- }
- public interface IMessage {
- void Message();
- } ** ** ** ** ** ** End of Interface Segregation Principle ** ** ** ** *
Open Closed Principle
OCP states Software Entities should be open for extension but closed for modification.
Here we are calculating the bonus of a teacher by taking a simple Console Applicaton
- public class Teacher
- {
- public int EmpId;
- public string Name;
- public Teacher(int id, string name)
- {
- this.EmpId = id;
- this.Name = name;
- }
- public decimal Bonus(decimal salary)
- {
- return salary * .2M;
- }
- }
- class Example2
- {
- public static void Main()
- {
- Teacher teacher = new Teacher(101, "Zeko");
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher.EmpId,teacher.Name,teacher.Bonus(10000));
- Console.ReadLine();
- }
- }
Teacher Employee ID: 101 Name: Zeko Bonus: 2000.0
Let's now assume that there is an enhancement of calculating the bonuses of Permanent and Temporary Teacher. To implement that we need to modify the existing class and its method . From here we could clearly say that it is violating the Open Closed Principle.
Violating the Rule
- public class Teacher
- {
- public int EmpId;
- public string Name;
- public string EmpType;
- public Teacher(int id, string name, string emptype)
- {
- this.EmpId = id;
- this.Name = name;
- this.EmpType = emptype;
- }
- public decimal Bonus(decimal salary)
- {
- if(EmpType=="Permanent")
- return salary * .2M;
- else
- return salary * .1M;
- }
- }
Permanent Teacher Employee ID: 101 Name: Zeko Bonus: 2000.0
Temporarty Teacher Employee ID: 102 Name: Priyanka Bonus: 1000.0
So what should we do?
To abide by the guidelines of OCP we would make the Teacher class as an abstract class having Bonus() as an abstract method.
- public abstract class Teacher
- {
- public int EmpId;
- public string Name;
- public Teacher(int id, string name)
- {
- this.EmpId = id;
- this.Name = name;
- }
- public abstract decimal Bonus(decimal salary);
- }
Now the school could easily calculate the bonus of their Permanent Teachers by implementing the below class
- public class PermanentTeacher : Teacher
- {
- public PermanentTeacher(int id, string name):base(id,name)
- {
- }
- public override decimal Bonus(decimal salary)
- {
- return salary * .2M;
- }
- }
-
- class Example2
- {
- public static void Main()
- {
- Teacher teacher = new PermanentTeacher(101, "Zeko");
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher.EmpId,teacher.Name,teacher.Bonus(10000));
- Console.ReadLine();
- }
- }
The advantage of using abstract class here is if the school in the future decides to give bonuses for their Temporary Teachers as well then we do not have to modify the Teacher class again as we did before, as it is open for extention now.
Add one more TemporaryTeacher class just like the Permanent Teacher .
Final Implementation
- public abstract class Teacher
- {
- public int EmpId;
- public string Name;
- public Teacher(int id, string name)
- {
- this.EmpId = id;
- this.Name = name;
- }
- public abstract decimal Bonus(decimal salary);
- }
-
- public class PermanentTeacher : Teacher
- {
- public PermanentTeacher(int id, string name):base(id,name)
- {
- }
- public override decimal Bonus(decimal salary)
- {
- return salary * .2M;
- }
- }
-
- public class TemporaryTeacher : Teacher
- {
- public TemporaryTeacher(int id, string name) : base(id, name)
- {
- }
- public override decimal Bonus(decimal salary)
- {
- return salary * .1M;
- }
- }
-
- class Example2
- {
- public static void Main()
- {
- Teacher teacher = new PermanentTeacher(101, "Zeko");
- Teacher teacher2 = new TemporaryTeacher(102, "Priyanka");
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher.EmpId,teacher.Name,teacher.Bonus(10000));
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher2.EmpId, teacher2.Name, teacher2.Bonus(10000));
- Console.ReadLine();
- }
- }
-
- Employee ID: 101 Name: Zeko Bonus: 2000.0
- Employee ID: 102 Name: Priyanka Bonus: 1000.0
-
- Hence the Teacher class is now open for extention but closed for modification which does not vioalte the OCP.
-
-
- ************End of Open Closed Principle*********
Liskov Substitution Principle
LSP states that Derived types can be completely substitutable for their base types. The point to note here is no new exception should be thrown by the subtype.
In our previous example of Open Closed Principle we have followed one rule of LSP where we are actually substituing the base class Teacher with the derived class PermanentTeacher and TemporaryTeacher
- class Example2
- {
- public static void Main()
- {
- Teacher teacher = new PermanentTeacher(101, "Zeko");
- Teacher teacher2 = new TemporaryTeacher(102, "Priyanka");
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher.EmpId,teacher.Name,teacher.Bonus(10000));
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher2.EmpId, teacher2.Name, teacher2.Bonus(10000));
- Console.ReadLine();
- }
- }
So , again let's assume that the school has made another call of hiring a Contract Teacher who will not get any bonus since the person is hired on contract.
To implement this let's add the another class similar to Permanent and Temporary Class.
- public class ContractTeacher : Teacher
- {
- public ContractTeacher(int id, string name) : base(id, name)
- {
- }
- public override decimal Bonus(decimal salary)
- {
- throw new NotImplementedException();
- }
- }
-
-
- class Example2
- {
- public static void Main()
- {
- Teacher teacher = new PermanentTeacher(101, "Zeko");
- Teacher teacher2 = new TemporaryTeacher(102, "Priyanka");
- Teacher teacher3 = new ContractTeacher(103, "Partha");
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher.EmpId,teacher.Name,teacher.Bonus(10000));
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher2.EmpId, teacher2.Name, teacher2.Bonus(10000));
- Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", teacher3.EmpId, teacher3.Name, teacher3.Bonus(50000));
- Console.ReadLine();
- }
- }
If you run the solution now we can see it violates the LSP since the subtype here is throwing an exception.
Solution
We will go ahead and modify the structure and add two separate interfaces. Refer to the below code.
Final Implementation
- interface IBonus
- {
- decimal Bonus(decimal salary);
- }
- interface ITeacher
- {
- int EmpId { get; set; }
- string Name { get; set; }
- decimal GetSalary();
- }
- public abstract class Teacher : ITeacher,IBonus
- {
- public int EmpId { get ; set; }
- public string Name { get ; set ; }
- public Teacher(int id, string name)
- {
- this.EmpId = id;
- this.Name = name;
- }
- public abstract decimal GetSalary();
- public abstract decimal Bonus(decimal salary);
- }
-
-
- public class PermanentTeacher : Teacher
- {
- public PermanentTeacher(int id, string name) : base(id, name)
- {
- }
- public override decimal GetSalary()
- {
- return 10000;
- }
- public override decimal Bonus(decimal salary)
- {
- return salary * .2M;
- }
- }
- public class TemporaryTeacher : Teacher
- {
- public TemporaryTeacher(int id, string name) : base(id, name)
- {
- }
- public override decimal GetSalary()
- {
- return 10000;
- }
- public override decimal Bonus(decimal salary)
- {
- return salary * .1M;
- }
- }
-
-
-
- public class ContractTeacher : ITeacher
- {
- public int EmpId { get ; set ; }
- public string Name { get ; set; }
- public ContractTeacher(int id, string name)
- {
- this.EmpId = id;
- this.Name = name;
- }
- public decimal GetSalary()
- {
- return 5000;
- }
- }
-
-
- class Example2
- {
- public static void Main()
- {
-
- Teacher teacher = new PermanentTeacher(101, "Zeko");
- Teacher teacher2 = new TemporaryTeacher(102, "Priyanka");
- Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} Bonus:{3}", teacher.EmpId,teacher.Name,teacher.GetSalary(),teacher.Bonus(teacher.GetSalary()));
- Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} Bonus:{3}", teacher2.EmpId, teacher2.Name, teacher2.GetSalary(), teacher2.Bonus(teacher2.GetSalary()));
-
- List<ITeacher> teachers = new List<ITeacher>();
- teachers.Add(new PermanentTeacher(101, "Zeko"));
- teachers.Add(new TemporaryTeacher(102, "Priyanka"));
- teachers.Add(new ContractTeacher(103, "Partha"));
- foreach (var obj in teachers)
- {
- Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} ", obj.EmpId, obj.Name, obj.GetSalary());
- }
- Console.ReadLine();
- }
- }
-
- ************End of Liskov Substitution Principle*********
Dependency Inversion Principle
As per DIP, high level modules should not depend on low level modules; rather both should depend on abstracions. Additionally abstraction should not depend on details. Details should depend on abstractions.
- public class Message
- {
- public void SendMessage()
- {
- Console.WriteLine("Message Sent");
- }
- }
- public class Notification
- {
- private Message _msg;
- public Notification()
- {
- _msg = new Message();
- }
- public void PromotionalNotification()
- {
- _msg.SendMessage();
- }
- }
- class Test_Notify
- {
- public static void Main()
- {
- Notification notify = new Notification();
- notify.PromotionalNotification();
- Console.WriteLine();
- Console.ReadLine();
- }
- }
Note
The above Notification class totally depends on Message class, because it only can send one type of notification. If we want to introduce two types of messages (Email,SMS) then we need to change the notification class also, hence it is called tightly coupled.
Now the best way to acheive the requirment is by implemention of Dependency Injection so that we can make it loosely coupled.
If the topic of DI is completely new please refer to
this url for better understanding along with its types.
Final Implemnetation
- public interface IMessage
- {
- void SendMessage();
- }
- public class Email : IMessage
- {
- public void SendMessage()
- {
- Console.WriteLine("Send Email");
- }
- }
- public class SMS : IMessage
- {
- public void SendMessage()
- {
- Console.WriteLine("Send Sms");
- }
- }
- public class Notification
- {
- private IMessage _msg;
- public Notification(IMessage msg)
- {
- this._msg = msg;
- }
- public void Notify()
- {
- _msg.SendMessage();
- }
- }
- class Test_Notify
- {
- public static void Main()
- {
- Email email = new Email();
-
- Notification notify = new Notification(email);
- notify.Notify();
- SMS sms = new SMS();
-
- notify = new Notification(sms);
- notify.Notify();
- Console.WriteLine();
- Console.ReadLine();
- }
- }
Happy Learning...