0 ratings0% found this document useful (0 votes) 174 views19 pagesSOLID C# Principles
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content,
claim it here.
Available Formats
Download as PDF or read online on Scribd
sarnarz021, 12:21 ‘SOLID Principles in Ct
BS c#Comer scomeéamember Login
we
SG SOLID Principles In C#
gS Damodhar Naidu Updated date Mar 16, 2021
2.5m 183 144
Download Free .NET & JAVA Office Files API
Try Free File Format APIs for Word/Excel/PDF
SOLID design principles in C# are basic design principles. SOLID stands for Single
Responsibility Principle (SRP), Open closed Principle (OSP), Liskov substitution Principle
(LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP).
Here let's learn basics of SOLID design principles using C# and .NET.
1. The reasons behind most unsuccessful applications
2. Solutions
3, Intro to SOLID principles
4. SRP
5.0CP
6.LSP
7.1SP
8, DIP
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl
srsarnarz021, 12:21 ‘SOLID Principles in Ct
SOLID,
The reason behind most unsuccessful applications
Developers start building applications with good and tidy designs using their knowledge and
experience. But over time, applications might develop bugs. The application design must be
altered for every change request or new feature request. After some time we might need to
put in a lot of effort, even for simple tasks and it might require full working knowledge of the
entire system. But we can't blame change or new feature requests. They are part of
software development. We can't stop them or refuse them either. So who is the culprit
here? Obviously it is the design of the application.
The following are the design flaws that cause damage in software, mostly.
1. Putting more stress on classes by assigning more responsibilities to them. (A lot of
functionality not related to a class.)
2. Forcing the classes to depend on each other. if classes are dependent on each other
(in other words tightly coupled), then a change in one will affect the other.
3. Spreading duplicate code in the system/application.
Solution
1, Choosing the correct architecture (in other words MVC, 3tier, Layered, MVP, MVVP and
so on).
2, Following Design Principles.
3. Choosing correct Design Patterns to build the software based on its specifications.
Now we go through the Design Principles first and will cover the rest soon.
Intro to SOLID principles
SOLID principles are the design principles that enable us to manage most of the software
design problems. Robert C. Martin compiled these principles in the 1990s. These principles
provide us with ways to move from tightly coupled code and little encapsulation to the
desired results of loosely coupled and encapsulated real needs of a business properly.
SOLID is an acronym of the following,
* S: Single Responsibility Principle (SRP)
* 0: Open closed Principle (OCP)
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 222sari2r023, 1221 ‘SOLID Principles nC
* L: Liskov substitution Principle (LSP)
© |: Interface Segregation Principle (ISP)
ependency Inversion Principle (DIP)
S: Single Responsibility Principle (SRP)
SRP says "Every software module should have only one reason to change".
DvINeIDe:
This means that every class, or similar structure, in your code should have only one job to
do. Everything in that class should be related to a single purpose. Our class should not be
like a Swiss knife wherein if one of them needs to be changed then the entire tool needs to
be altered. It does not mean that your classes should only contain one method or property.
There may be many members as long as they relate to single responsibility.
The Single Responsibility Principle gives us a good way of identifying classes at the design
phase of an application and it makes you think of all the ways a class can change. A good
separation of responsibilities is done only when we have the full picture of how the
application should work. Let us check this with an example.
e1. | public class Userservice
e2. | {
@3. public void Register(string email, string password)
24, {
es. if (!Validatemail(email))
es. throw new ValidationException("Email is not an email");
7. var user = new User(email, password);
e8.
e9.
SendEmail(new MailMessage("mysite@nowhere. cor
10. }
» email) { Subject="HEllc
1. public virtual bool ValidateEmail(string email)
12. {
13. return email.Contains("@");
14, }
15. public bool SendEmail(MailMessage message)
16. {
17. _smtpClient .Send(message) ;
18. }
aa ly
hitpslwwn.c-sharpcomer com/UploadFle/éamubathalsoli-princples-in-C-Sharpl 322sarnarz02t, 12:21 ‘SOLID Principles in Ct
woes
It looks fine, but it is not following SRP. The SendEmail and ValidateEmail methods have
nothing to do within the UserService class. Let's refract it.
@1. | public class UserService
e2. | 4
@3. Emailservice _emailservice;
4. DbContext _dbContext;
es.
public UserService(Emailservice aEmailService, DbContext aDbContext)
6. {
e7. _emailService = aEmailService;
8. “dbContext = aDbContext;
@9.
10. public void Register(string email, string password)
11. {
12. if (!_emailservice.ValidateEmail(email))
13. throw new ValidationException("Email is not an email");
14. var user = new User(email, password) ;
15. _dbContext.Save(user) ;
16.
emailService.Sendémail(new MailMessage("myname@mydomain.com", email) {S
17.
18. }
19. }
20. public class EmailService
21. {
22. SmtpClient _smtpClient;
23. public EmailService(SmtpClient aSmtpClient)
24. {
25. _smtpClient = aSmtpClient;
26. }
27. public bool virtual Validatetmail(string email)
28. {
29. return email.Contains("@");
30. }
31. public bool SendEmail(MailMessage message)
32. {
33. _smtpClient .Send(message) ;
34, }
35. | }
0: Open/Closed Principle
The Open/closed Principle says "A software module/class is open for extension and closed
for modification".
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 4122sarnarz021, 12:21 ‘SOLID Principles in Ct
OPEN CLOSED PRINCIPLE
Here "Open for extension" means, we need to design our module/class in such a way that
the new functionality can be added only when new requirements are generated. "Closed for
modification" means we have already developed a class and it has gone through unit
testing. We should then not alter it until we find bugs. As it says, a class should be open for
extensions, we can use inheritance to do this. Okay, let's dive into an example.
Suppose we have a Rectangle class with the properties Height and Width.
@1. | public class Rectangle{
e2. public double Height {get;set;}
@3. public double Wight {get;set; }
ea. | }
Our app needs the ability to calculate the total area of a collection of Rectangles. Since we
already learned the Single Responsibility Principle (SRP), we don't need to put the total area
calculation code inside the rectangle. So here | created another class for area calculation.
@1. | public class AreaCalculator {
e2. public double TotalArea(Rectangle[] arrRectangles)
@3. {
ea. double area;
es. foreach(var objRectangle in arrRectangles)
es. {
@7. area += objRectangle.Height * objRectangle.Width;
es. }
es. return area;
10. }
a1. |}
Hey, we did it. We made our app without violating SRP. No issues for now. But can we
extend our app so that it could calculate the area of not only Rectangles but also the area of
Circles as well? Now we have an issue with the area calculation issue because the way to do
circle area calculation is different. Hmm. Not a big deal. We can change the TotalArea
method a bit so that it can accept an array of objects as an argument. We check the object
type in the loop and do area calculation based on the object type.
e1. | public class Rectangle{
2. | public double Height {get;set;}
hitps:hwwnc-sharpcomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 522sarnarz02t, 12:21 ‘SOLID Principles in Ct
@3. public double Wight {get;set; }
4.
@5. | public class Circle¢
06. public double Radius {get;set;}
@7.
@8. | public class AreaCalculator
a. | {
10. public double TotalArea(object[] arrdbjects)
11. {
12. double area = @;
13. Rectangle objRectangle;
14, Circle objcircle;
15. foreach(var obj in arrobjects)
16. {
a7. if(obj is Rectangle)
18.
19. area += obj.Height * obj.Width;
20. }
21. else
22. {
23. objCircle = (Circle)obj;
24, area += objCircle.Radius * objCircle.Radius * Math.PI;
25. }
26. }
27. return area;
28. }
29. | }
Wow. We are done with the change. Here we successfully introduced Circle into our app. We
can add a Triangle and calculate it's the area by adding one more "if" block in the TotalArea
method of AreaCalculator. But every time we introduce a new shape we need to alter the
TotalArea method. So the AreaCalculator class is not closed for modification. How can we
make our design to avoid this situation? Generally, we can do this by referring to
abstractions for dependencies, such as interfaces or abstract classes, rather than using
concrete classes. Such interfaces can be fixed once developed so the classes that depend
upon them can rely upon unchanging abstractions. Functionality can be added by creating
new classes that implement the interfaces. So let's refract our code using an interface.
@1. | public abstract class Shape
e2. | {
@3. public abstract double Area();
ea. |}
Inheriting from Shape, the Rectangle and Circle classes now look like this:
e1. | public class Rectangle: Shape
a2. | {
@3. public double Height {get;set;}
ea. public double Width {get;set;}
es. public override double Area()
es. {
@7. return Height * Width;
8. }
es. | }
hitps:hwwnc-sharpcomer com/UploadFle/éamubethalsoli-principles-in-C-Sharpl e122sarnarz02t, 12:21 ‘SOLID Principles in Ct
10. | public class Circle: Shape
1. | {
12. public double Radius {get;set;}
public override double Area()
t
return Radius * Radus * Math.PI;
}
Every shape contains its area with its own way of calculation functionality and our
AreaCalculator class will become simpler than before.
e1. | public class AreaCalculator
a2. | {
@3. public double TotalArea(Shape[] arrShapes)
ea. {
es. double area=0;
es. foreach(var objShape in arrShapes)
@7. {
es. area += objShape.Area();
}
return area;
y
Now our code is following SRP and OCP both. Whenever you introduce a new shape by
deriving from the "Shape" abstract class, you need not change the "AreaCalculator" class.
Awesome. Isn't it?
L: Liskov Substitution Principle
oa:
The Liskov Substitution Principle (LSP) states that "you should be able to use any derived
class instead of a parent class and have it behave in the same manner without
modification". It ensures that a derived class does not affect the behavior of the parent
class, in other words,, that a derived class must be substitutable for its base class.
This principle is just an extension of the Open Closed Principle and it means that we must
ensure that new derived classes extend the base classes without changing their behavior. |
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 22sarnarz021, 12:21 ‘SOLID Principles in Ct
will explain this with a real-world example that violates LSP.
A father is a doctor whereas his son wants to become a cricketer. So here the son can't
replace his father even though they both belong to the same family hierarchy.
Now jump into an example to learn how a design can violate LSP. Suppose we need to build
an app to manage data using a group of SQL files text. Here we need to write functionality
to load and save the text of a group of SQL files in the application directory. So we need a
class that manages the load and saves the text of group of SQL files along with the SqlFile
Class.
e1. | public class sqlFile
ez. | {
@3. public string FilePath {get;set;}
ea, public string FileText {get;set;}
es. public string LoadText()
es. {
e7. /* Code to read text from sql file */
28. }
@9. public string SaveText()
10. {
qt. /* Code to save text into sql file */
12. }
13. | }
14. | public class SqlFileManager
15s. | {
16. public List IstsqlFiles {get;set}
17.
18. public string GetTextFromFiles()
19. {
20. StringBuilder objStrBuilder = new StringBuilder();
21. foreach(var objFile in lstSqlFiles)
22. {
23. objStrBuilder .Append(objFile.LoadText()) 5
24, }
25. return objStrBuilder. ToString();
26.
27. public void SaveTextIntoFiles()
28. {
29. foreach(var objFile in 1stSqlFiles)
30. {
31. objFile.SaveText();
32. }
33. }
34. | }
OK. We are done with our part. The functionality looks good for now. After some time our
leaders might tell us that we may have a few read-only files in the application folder, so we
need to restrict the flow whenever it tries to do a save on them.
OK. We can do that by creating a "ReadOnlySqlFile” class that inherits the "SqIFile” class and
we need to alter the SaveTextintoFiles() method by introducing a condition to prevent calling
the SaveText() method on ReadOnlySqlFile instances.
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl e122sarnarz021, 12:21 ‘SOLID Principles in Ct
e1.
e2.
@3.
4.
es.
es.
@7.
e8.
9.
10.
11.
12.
13.
14,
1s.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
public class SqlFile
{
public string LoadText()
{
/* Code to read text from sql file */
+
public void SaveText()
{
/* Code to save text into sql file */
+
}
public class ReadOnlySqiFile: SqlFile
{
public string FilePath {get;set;}
public string FileText {get;set;}
public string LoadText()
{
/* Code to read text from sql file */
}
public void SaveText()
{
/* Throw an exception when app flow tries to do save. */
‘throw new TOException("Can't Save");
+
?
To avoid an exception we need to modify "SqlFileManager" by adding one condition to the
loop.
e1.
e2.
3.
4.
5.
es.
@7.
es.
9.
10.
11.
12.
13.
14,
15.
16.
17,
18,
19.
20.
21.
22.
23.
24.
public class SqlFileManager
{
public List aLstReadableFiles
ea. {
es. StringBuilder objstrBuilder = new StringBuilder();
es. foreach(var objFile in aLstReadableFiles)
bps thaw c-sharpcornercon/UploadFleidamubethalsold principles f-C-Sharpl 10722sarnarz021, 12:21 ‘SOLID Principles in Ct
@7 {
es. objStrBuilder .Append(objFile.LoadText());
eo. }
: return objStrBuilder.ToString();
}
public void SaveTextIntoFiles(List alstWritableFiles)
{
foreach(var objFile in alstwritableFiles)
{
objFile.SaveText();
}
}
Here the GetTextFromFiles() method gets only the list of instances of classes that implement
the IReadOnlySq[File interface. That means the Sq|File and ReadOnlySqjFile class instances.
And the SaveTextIntoFiles() method gets only the list instances of the class that implements
the IWritableSqlFiles interface, in other words, SqjFile instances in this case. Now we can say
our design is following the LSP. And we fixed the problem using the Interface segregation
principle by (ISP) identifying the abstraction and the responsibility separation method.
|: Interface Segregation Principle (ISP)
The interface Segregation Principle states "that clients should not be forced to implement
interfaces they don't use. Instead of one fat interface, many small interfaces are preferred
based on groups of methods, each one serving one submodule.”.
NG oO Ree OE
We can define it in another way. An interface should be more closely related to the code
that uses it than code that implements it. So the methods on the interface are defined by
which methods the client code needs rather than which methods the class implements. So
clients should not be forced to depend upon interfaces that they don't use.
Like classes, each interface should have a specific purpose/responsibility (refer to SRP). You
shouldn't be forced to implement an interface when your object doesn't share that purpose.
The larger the interface, the more likely it includes methods that not all implementers can
hitps:lwwc-sharpcomer com/UploadFile/éamubsthafsoli-principles-n-C-Sharpl 112sainarz02t, 12:21 ‘SOLID Principles in Ct
do. That's the essence of the Interface Segregation Principle. Let's start with an example that
breaks the ISP. Suppose we need to build a system for an IT firm that contains roles like
TeamLead and Programmer where TeamLead divides a huge task into smaller tasks and
assigns them to his/her programmers or can directly work on them.
Based on specifications, we need to create an interface and a TeamLead class to implement
it,
e1.
e2.
@3.
4.
es.
es.
@7.
e8.
e9.
10.
11.
12.
13.
14,
1s.
1s.
17.
18.
19.
20.
21.
public Interface ILead
{
void CreateSubTask() ;
void AssginTask();
void WorkOnTask();
public class TeamLead : ILead
‘ public void AssignTask()
‘ //Code to assign a task.
public void CreatesubTask()
‘ /ICode to create a sub task
public void WorkonTask()
‘ //Code to implement perform assigned task.
) +
OK. The design looks fine for now. Later another role like Manager, who assigns tasks to
TeamLead and will not work on the tasks, is introduced into the system. Can we directly
implement an |Lead interface in the Manager class, like the following?
e1.
e2.
23.
4.
8s.
e6.
e7.
es.
9.
10.
11.
12.
13.
14,
15.
public class Manager: ILead
‘ public void AssignTask()
‘ //Code to assign a task.
dubtic void CreateSubTask()
‘ //Code to create a sub task.
dabtic void WorkonTask()
‘ throw new Exception("Manager can't work on Task");
) }
Since the Manager can't work on a task and at the same time no one can assign tasks to the
Manager, this WorkOnTask() should not be in the Manager class. But we are implementing
this class from the Lead interface, we need to provide a concrete Method. Here we are
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl ra122sainarz02t, 12:21 ‘SOLID Principles in Ct
forcing the Manager class to implement a WorkOnTask() method without a purpose. This is
wrong. The design violates ISP. Let's correct the design.
Since we have three roles, 1. Manager, that can only divide and assign the tasks, 2.
TeamLead that can divide and assign the tasks and can work on them as well, 3. The
programmer that can only work on tasks, we need to divide the responsibilities by
segregating the |Lead interface. An interface that provides a contract for WorkOnTask().
01. | public interface IProgrammer
e2. | {
@3. void WorkOnTask();
e4. | }
An interface that provides contracts to manage the tasks:
@1. | public interface ILead
e2. | {
83. void AssignTask();
e4. void CreateSubTask();
es. |}
Then the implementation becomes:
@1. | public class Programmer: IProgrammer
a2. | ¢
@3. public void WorkOnTask()
es. {
@5. //code to implement to work on the Task.
es. }
@7.
@8. | public class Manager: ILead
a. | {
10. public void AssignTask()
11. {
12. //Code to assign a Task
13. }
14, public void CreateSubTask()
15. {
16. //Code to create a sub taks from a task.
17. }
as. | }
TeamLead can manage tasks and can work on them if needed. Then the TeamLead class
should implement both of the IProgrammer and ILead interfaces.
@1. | public class TeamLead: IProgrammer, ILead
a2. | {
@3. public void AssignTask()
ea. {
es. //Code to assign a Task
es.
@7. public void CreateSubTask()
es. {
e9, //Code to create a sub task from a task.
10. }
hitps:hwwc-sharpcomer com/UploadFle/éamubethalsoli-principles-in-C-Sharpl 19122sarnarz021, 12:21 ‘SOLID Principles in Ct
1. public void WorkOnTask()
12. {
13. //code to implement to work on the Task.
Wow. Here we separated responsibilities/purposes and distributed them on multiple
interfaces and provided a good level of abstraction too.
D: Dependency Inversion Principle
The Dependency Inversion Principle (DIP) states that high-level modules/classes should not
depend on low-level modules/classes. Both should depend upon abstractions. Secondly,
abstractions should not depend upon details. Details should depend upon abstractions.
Neg
High-level modules/classes implement business rules or logic in a system (application). Low-
level modules/classes deal with more detailed operations; in other words they may deal
with writing information to databases or passing messages to the operating system or
services.
A high-level module/class that has a dependency on low-level modules/classes or some
other class and knows a lot about the other classes it interacts with is said to be tightly
coupled. When a class knows explicitly about the design and implementation of another
class, it raises the risk that changes to one class will break the other class. So we must keep
these high-level and low-level modules/classes loosely coupled as much as we can. To do
that, we need to make both of them dependent on abstractions instead of knowing each
other. Let's start with an example.
Suppose we need to work on an error logging module that logs exception stack traces into a
file. Simple, isn’t it? The following are the classes that provide the functionality to log a stack
trace into a file.
e1. | public class FileLogger
e2. | {
@. public void LogMessage(string aStackTrace)
e4. {
@. /Icode to log stack trace into a file.
hitps:wwn.c-sharpomer com/UploadFile/éamubetharsoli-principles-n-C-Sharpl saizasarnarz021, 12:21
es. }
e7. | }
a8. | public class ExceptionLogger
es. | {
10. public void LogIntoFile(Exception aException)
11. {
12. FileLogger objFileLogger = new FileLogger();
13. objFileLogger. LogMessage (GetUserReadableMessage(aException));
14. }
15. private GetUserReadableMessage(Exception ex)
16. {
17. string strMessage = string. Empty;
18.
//code to convert Exception's stack trace and message to user readable
19. sees
20. sees
21. return strMessage;
22. }
23. | }
Aclient class exports data from many files to a database.
@1. | public class Data€xporter
e2. | {
@3. public void ExportDataFromFile()
e4. {
es. try {
e6. //code to export data from files to database.
7. }
es. catch(Exception ex)
@9. {
10. new ExceptionLogger().LogIntoFile(ex);
11. }
22. |}
13. |}
Looks good. We sent our application to the client. But our client wants to store this stack
trace in a database if an 1O exception occurs. Hmm... okay, no problem. We can implement
that too. Here we need to add one more class that provides the functionality to log the stack
trace into the database and an extra method in ExceptionLogger to interact with our new
class to log the stack trace.
e1. | public class DbLogger
a2. | ¢
83. public void LogMessage(string aMessage)
es. {
@5. //Code to write message in database.
es. }
e7. |
@8. | public class FileLogger
a. | {
10. public void LogMessage(string aStackTrace)
11. {
12. //code to log stack trace into a file.
13. }
hitpslwwnc-sharpomer com/UploadFile/éamubethalsoli-princples-in-C-Sharpl 19122sainarz0t, 12:21 ‘SOLID Principles in Ct
oa 2 pric class ExceptionLogger
o ‘ public void LogintoFile(Exception aException)
i ‘ FileLogger objFileLogger = new FileLogger();
20. objFileLogger . LogMessage (GetUserReadableMessage(aException));
2 » pic void LogIntoDataBase(Exception aException)
ae ‘ DbLogger objDbLogger = new DbLogger();
25. objDbLogger.. LogMessage(GetUserReadablemessage(aException)) ;
y > vate string GetUserReadableMessage(Exception ex)
a ‘ string strMessage = string.Empty;
38.
//code to convert Exception's stack trace and message to user readable
31, sees
32. sees
33. return strMessage;
34, }
35.
36. | public class DataExporter
37. | {
38. public void ExportDataFromFile()
39. {
40. try {
41. //code to export data from files to database.
42. +
43. catch(IOException ex)
44. {
45. new ExceptionLogger().LogIntoDataBase(ex) ;
46. }
47. catch(Exception ex)
48. {
49. new ExceptionLogger().LogIntoFile(ex);
50. }
51. }
52. | }
Looks fine for now. But whenever the client wants to introduce a new logger, we need to
alter ExceptionLogger by adding a new method. If we continue doing this after some time
then we will see a fat ExceptionLogger class with a large set of methods that provide the
functionality to log a message into various targets. Why does this issue occur? Because
ExceptionLogger directly contacts the low-level classes FileLogger and DbLogger to log the
exception. We need to alter the design so that this ExceptionLogger class can be loosely
coupled with those classes. To do that we need to introduce an abstraction between them
so that ExcetpionLogger can contact the abstraction to log the exception instead of
depending on the low-level classes directly.
@1. | public interface ILogger
e2. | {
@. void LogMessage(string aString) ;
ea. | }
hitps:hwwnc-sharpomer com/UploadFile/éamubathalsoli-princples-in-C-Sharpl 16122sainarz02t, 12:21 ‘SOLID Principles in Ct
Now our low-level classes need to implement this interface.
@1. | public class DbLogger: ILogger
a2. | ¢
83. public void LogMessage(string aMessage)
ea. {
@5. //Code to write message in database.
es. }
e7. | >
@8. | public class FileLogger: TLogger
a. | {
10. public void LogMessage(string aStackTrace)
11. {
12. //code to log stack trace into a file.
13. }
aa. | }
Now, we move to the low-level class's initiation from the ExcetpionLogger class to the
DataExporter class to make ExceptionLogger loosely coupled with the low-level classes
FileLogger and EventLogger. And by doing that we are giving provision to DataExporter class
to decide what kind of Logger should be called based on the exception that occurs.
e1. | public class ExceptionLogger
a2. | {
@3. private ILogger _logger;
e4. public ExceptionLogger(ILogger aLogger)
es. t
es. this. _logger = aLogger;
@7. }
e8. public void Logexception(Exception aException)
es. {
10. string strMessage = GetUserReadableMessage(aException) ;
11. this. logger. LogMessage(strMessage) ;
12. }
13. private string GetUserReadableMessage(Exception aException)
14. {
15. string strMessage = string. Empty;
16.
//code to convert Exception's stack trace and message to user readable
17. sees
18. see
19. return strMessage;
20. }
a. |}
22. | public class DataExporter
23. | {
24. public void ExportDataFromFile()
25. {
26. ExceptionLogger _exceptionLogger}
27. try {
28. //code to export data from files to database.
29. }
30. catch(IOException ex)
31. {
32. _exceptionLogger = new ExceptionLogger(new DbLogger());
33. “exceptionLogger . Logexception(ex) ;
34. y
hitps:won-sharpcomer com/UploadFile/éamubothalsoli-princples-in-C-Sharpl rieasainarz02t, 12:21 ‘SOLID Principles in Ct
35. catch(Exception ex)
36. t
37. _exceptionLogger = new ExceptionLogger(new FileLogger());
38. TexceptionLogger .Logexception(ex);
39. }
40. }
a. |}
We successfully removed the dependency on low-level classes. This ExceptionLogger doesn't
depend on the FileLogger and EventLogger classes to log the stack trace. We don't need to
change the ExceptionLogger's code anymore for any new logging functionality. We need to.
create a new logging class that implements the ILogger interface and must add another
catch block to the DataExporter class's ExportDataFromFile method.
@1. | public class EventLogger: ILogger
a2. | ¢
@3. public void LogMessage(string aMessage)
04. {
@5. //Code to write message in system's event viewer.
es. }
e7. |}
And we need to add a condition in the DataExporter class as in the following:
@1. | public class DataExporter
e2. | {
8. public void ExportDataFromFile()
ea. {
es. ExceptionLogger _exceptionLogger;
es. try {
@7. //code to export data from files to database.
es. }
e. catch(IOException ex)
10. {
11. _exceptionLogger = new ExceptionLogger(new DbLogger());
12. ~exceptionLogger .LogException(ex) ;
13. +
14. catch(Sqlexception ex)
15. {
16. _exceptionLogger = new ExceptionLogger(new EventLogger()) 5
17. _exceptionLogger . Logexception(ex) ;
18. }
19. catch(Exception ex)
20. {
21. _exceptionLogger = new ExceptionLogger(new FileLogger());
22. _exceptionLogger .LogException(ex) ;
2. }
24. }
2s. | }
Looks good. But we introduced the dependency here in the DataExporter class's catch
blocks. Yeah, someone must take the responsibility to provide the necessary objects to the
ExceptionLogger to get the work done.
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 9122sainarz02t, 12:21 ‘SOLID Principles in Ct
Let me explain it with a real-world example. Suppose we want to have a wooden chair with
specific measurements and the kind of wood to be used to make that chair. Then we can't
leave the decision making on measurements and the wood to the carpenter. Here his job is
to make a chair based on our requirements with his tools and we provide the specifications
to him to make a good chair.
So what is the benefit we get by the design? Yes, we definitely have a benefit with it. We
need to modify both the DataExporter class and ExceptionLogger class whenever we need
to introduce a new logging functionality. But in the updated design we need to add only
another catch block for the new exception logging feature. Coupling is not inherently evil. If
you don't have some amount of coupling, your software will not do anything for you. The
only thing we need to do is understand the system, requirements, and environment
properly and find areas where DIP should be followed
Great, we have gone through all five SOLID principles successfully. And we can conclude
that using these principles we can build an application with tidy, readable and easily
maintainable code.
Here you may have some doubt. Yes, about the quantity of code. Because of these
principles, the code might become larger in our applications. But my dear friends, you need
to compare it with the quality that we get by following these principles. Hmm, but anyway
27 lines are much fewer than 200 lines.
This is my little effort to share the uses of SOLID principles. | hope you enjoyed this article.
Images courtesy :https://lostechies.com/derickbailey/2009/02/1 1/solid-development-
principles-in-motivational-pictures/
Thank you,
Damu
C#SOLID ) | MVC) MVP ) | SOLID in C# | SOLID principles
Next Recommended Reading
Liskov Substitution Principle in C#
OUR BOOKS
hitpshwwnc-sharpomer com/UploadFile/éamubathalsoli-principles-in-C-Sharpl 9122