S.O.L.I.D is an acronym for the first five object-oriented design(OOD) principles . These principles, when combined together, make it easy for a programmer to develop software that is easy to maintain and extend. They also make it easy for developers to avoid code smells, easily refactor code and are also a part of agile or adaptive software development.
One Class should be responsible for one task.
class DataAccess
{
public static void InsertData()
{
Console.WriteLine("Data inserted into database successfully");
Console.WriteLine( "Logged Time:" + DateTime.Now.ToLongTimeString() + " Log Data insertion completed successfully");
}
}
So tomorrow if you want add a new logging like event viewer or File I/O then we need to go and change the “DataAccess”class, which is not right.
It’s like if “JOHN” has a problem why do I need to check “BOB”.
// Data access class is only responsible for data base related operations
class DataAccess
{
public static void InsertData()
{
Console.WriteLine("Data inserted into database successfully");
}
}
// Logger class is only responsible for logging related operations
class Logger
{
public static void WriteLog()
{
Console.WriteLine( "Logged Time:" + DateTime.Now.ToLongTimeString() + " Log Data insertion completed successfully");
}
}
we should strive to write code that doesn’t have to be changed every time the requirements change. How we do that can differ a bit depending on the context, such as our programming language.
Create a Base class with Required functionality, and ensure we will not modify that class. (Closed for modification)
Create a Derived class by inheriting the Base class for extension (Open for modification)
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
}
public class AreaCalculator
{
public double Area(Rectangle[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Width*shape.Height;
}
return area;
}
}
If we want to calculate the area of not only rectangles but of circles as well.
public double Area(object[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
Rectangle rectangle = (Rectangle) shape;
area += rectangle.Width*rectangle.Height;
}
else
{
Circle circle = (Circle)shape;
area += circle.Radius * circle.Radius * Math.PI;
}
}
return area;
}
AreaCalculator isn’t closed for modification as we need to change it in order to extend it. Or in other words: it isn’t open for extension. In a real world scenario where the code base is ten, a hundred or a thousand times larger and modifying the class means redeploying it’s assembly/package.
One way of solving this puzzle would be to create a base class for both rectangles and circles as well as any other shapes which defines an abstract method for calculating it’s area.
public abstract class Shape
{
public abstract double Area();
}public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width*Height;
}
}public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius*Radius*Math.PI;
}
}
As we’ve moved the responsibility of actually calculating the area away from AreaCalculator’s Area method it is now much simpler and robust as it can handle any type of Shape that we throw at it.
public double Area(Shape[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Area();
} return area;
}
If any module is using a Base class then the reference to that Base class can be replaced with a Derived class without affecting the functionality of the module.
we must make sure that new derived classes are extending the base classes without changing their behavior. If we are calling a method defined at a base class upon an abstracted class, the function must be implemented properly on the subtype class.
Let’s say our system wants to calculate discounts for Enquiries. Now Enquiries are not actual customer’s they are just leads. Because they are just leads we do not want to save them to database for now.
So we create a new class called as Enquiry which inherits from the “Customer” class. We provide some discounts to the enquiry so that they can be converted to actual customers and we override the “Add’ method with an exception so that no one can add an Enquiry to the database.
class Enquiry : Customer
{
public override double getDiscount(double TotalSales)
{
return base.getDiscount(TotalSales) - 5;
}
public override void Add()
{
throw new Exception("Not allowed");
}
}
So as per polymorphism rule parent “Customer” class object can point to any of it child class objects i.e. “Gold”, “Silver” or “Enquiry” during runtime without any issues.
List<Customer> Customers = new List<Customer>();
Customers.Add(new SilverCustomer());
Customers.Add(new goldCustomer());
Customers.Add(new Enquiry());
foreach (Customer o in Customers)
{
o.Add(); //throw exception for Enquiry
}
As per the inheritance hierarchy the “Customer” object can point to any one of its child objects and we do not expect any unusual behavior.
But when “Add” method of the “Enquiry” object is invoked it leads to exception. In other words the “Enquiry” has discount calculation , it looks like a “Customer” but IT IS NOT A CUSTOMER. So the parent cannot replace the child object seamlessly. In other words “Customer” is not the actual parent for the “Enquiry”class. “Enquiry” is a different entity altogether.
interface IDiscount
{
double getDiscount(double TotalSales);
}
interface IDatabase
{
void Add();
}
class Enquiry : IDiscount
{
public double getDiscount(double TotalSales)
{
return TotalSales - 5;
}
}
class Customer : IDiscount, IDatabase
{
public virtual void Add()
{
// Database code goes here
}
public virtual double getDiscount(double TotalSales)
{
return TotalSales;
}
}
In case we make a mistake of adding “Enquiry” class to the list compiler would complain.
List<Customer> Customers = new List<Customer>();
Customers.Add(new SilverCustomer());
Customers.Add(new goldCustomer());
Customers.Add(new Enquiry());//error
Clients should not be forced to depend upon interfaces that they do not use.
interface IToy {
void setPrice(int price);
void setColor(String color);
void move();
void fly();
}class ToyHouse :IToy {
int price;
String color;
public void setPrice(double price) {
this.price = price;
}
public void setColor(String color) {
this.color=color;
}
public void move(){
throw new Exception(“Not allowed”);
}
public void fly(){
throw new Exception(“Not allowed”);
}
}
ToyHouse
needs to provide implementation of the move()
and fly()
methods, even though it does not require them. This is a violation of the Interface Segregation Principle. Such violations affect code readability and confuse programmers. Violation of the Interface Segregation Principle also leads to violation of the complementary Open Closed Principle. As an example, consider that the Toy
interface is modified to include a walk()
method to accommodate toy robots. As a result, you now need to modify all existing Toy implementation classes to include a walk()
method even if the toys don’t walk. In fact, the Toy implementation classes will never be closed for modifications, which will lead to a fragile application that is difficult and expensive to maintain.
By following the Interface Segregation Principle, you can address the main problem of the toy building application- The Toy
interface forces clients (implementation classes) to depend on methods that they do not use.
The solution is- Segregate the Toy
interface into multiple role interfaces each for a specific behavior. Let’s segregate the Toy
interface, so that our application now have three interfaces: Toy
, Movable
, and Flyable
.
interface IToy {
void setPrice(int price);
void setColor(String color);
}
interface IMovable {
void move();
}
interface IFlyable {
void fly();
}
As all toys will have a price and color, all Toy implementation classes can implement this interface. Then, we wrote the Movable
and Flyable
interfaces to represent moving and flying behaviors in toys.
class ToyHouse :IToy {
int price;
String color;
public void setPrice(double price) {
this.price = price;
}
public void setColor(String color) {
this.color=color;
}
}
Class ToyPlane implements IToy, IMovable, IFlyable {double price;
String color;public void setPrice(int price) {
this.price = price;
}public void setColor(String color) {
this.color=color;
}public void move(){//code related to moving plane}public void fly(){// code related to flying plane}
}
There are times when you might need your interface to have multiple methods, and that’s ok.Include methods which are all very specific to the interface and the client will most likely want to interact with them, therefore packaging them together in the same interface is the right thing to do.
Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.Abstractions should not depend on details. Details should depend on abstractions
With traditional programming, the main function of an application might make function calls into a menu library to display a list of available commands and query the user to select one. The library thus would return the chosen option as the value of the function call, and the main function uses this value to execute the associated command. In this interaction, my code is in control: it decides when to ask questions, when to read responses, and when to process those results.
With inversion of control, on the other hand, the program would be written using a software framework that knows common behavioral and graphical elements, such as windowing systems, menus, controlling the mouse, and so on. The custom code “fills in the blanks” for the framework, such as supplying a table of menu items and registering a code subroutine for each item, but it is the framework that monitors the user’s actions and invokes the subroutine when a menu item is selected.
In the command line form I control when these methods are called, but in the window example I don’t. Instead I hand control over to the windowing system. It then decides when to call my methods, based on the bindings I made when creating the form. The control is inverted - it calls me rather me calling the framework. This phenomenon is Inversion of Control (also known as the Hollywood Principle - "Don't call us, we'll call you").
So rather than the internal program controlling the flow, events drive the program flow. Event flow approach is more flexible as their no direct invocation which leads to more flexibility. You can delegate the control flow by callback delegates, observer pattern, events, DI (Dependency injection) and lot of other ways.
Problem: You have classes that have dependencies on services or components whose concrete type is specified at design time. In this example, ClassA has dependencies on ServiceA and ServiceB. Figure 1 illustrates this.
This situation has the following problems:
Any of the following conditions justifies using the solution described in this pattern:
Delegate the function of selecting a concrete implementation type for the classes’ dependencies to an external component or source.
The Inversion of Control pattern can be implemented in several ways. The Dependency Injection pattern and the Service Locator pattern are specialized versions of this pattern that delineate different implementations. Figure 2 illustrates the conceptual view of both patterns.
Service Locator:
Create a service locator that contains references to the services and that encapsulates the logic to locate them. In your classes, use the service locator to obtain service instances. The service locator does not instantiate the services. It provides a way to register services and it holds references to the services. After the service is registered, the service locator can find the service.
public interface IServiceLocator { T GetService<T>(); }
Now let’s see a very simple implementation of this contract:
class ServiceLocator : IServiceLocator {
// map that contains pairs of interfaces and
// references to concrete implementations
private IDictionary<object, object> services;
internal ServiceLocator() {
services = new Dictionary<object, object>(); // fill the map this.services.Add(typeof(IServiceA), new ServiceA()); this.services.Add(typeof(IServiceB), new ServiceB()); this.services.Add(typeof(IServiceC), new ServiceC());
}
public T GetService<T>() {
try {
return (T)services[typeof(T)];
}
catch (KeyNotFoundException) {
throw new ApplicationException("The requested service is not registered");
}
}
}
The generic GetService() method returns a reference the correct implementation fetching it from the dictionary .This is how a client would invoke the service:
IServiceLocator locator = new ServiceLocator();
IServiceA myServiceA = locator.GetService<IServiceA>();
The clients do not know the actual classes implementing the service. They only have to interact with the service locator to get to an implementation.
Declaratively express dependencies in your class definition. Use a Builder object to obtain valid instances of your object’s dependencies and pass them to your object during the object’s creation and/or initialization.
public class ManagementController : Controller
{
private readonly ITenantStore tenantStore;
public ManagementController(ITenantStore tenantStore)
{
this.tenantStore = tenantStore;
}
public ActionResult Index()
{
var model = new TenantPageViewData<IEnumerable<string>>
(this.tenantStore.GetTenantNames())
{
Title = "Subscribers"
};
return this.View(model);
}
}
ManagementController constructor receives an ITenantStore instance as a parameter, injected by some other class. The only dependency in the ManagementContoller class is on the interface type. This is better because it doesn’t have any knowledge of the class or component that is responsible for instantiating the ITenantStore object.
The class that is responsible for instantiating the TenantStore object and inserting it into the ManagementController class is called the DependencyInjectionContainer class.
Thanks, for reading the blog, I hope it helps you. Please share this link on your social media accounts so that others can read our valuable content. Share your queries with our expert team and get Free Expert Advice for Your Business today.
Hire me on Linkedin
My portfolio