DEV Community

Carlos Vega
Carlos Vega

Posted on

Simple SOLID

From by blog at https://veglos.github.io/posts/simple-solid/

Table of contents

  1. Single-Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

1. Single-Responsibility Principle

There should never be more than one reason for a class to change.

Violation

/* This class has two reasons to change: it's properties and storing data in the database */
public class Customer {

    public string Name {get;set}
    public string LastName {get;set}
    public string Email {get;set}

    private readonly string _repository;

    public Customer(string repository, string name, string lastName, string email) {
        _repository = repository;
        Name = name;
        LastName = lastName;
        Email = email;
    }

    public void Save() {

        if(_repository == "SQLServer") {
            /* Saving in SQLServer*/    
        }
        else if(_repository == "MongoDB") {
            /* Saving in MongoDB*/
        }
        else{
            throw new Exception("Database not supported");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Compliance

/* This class now cares only about it's properties*/
public class Customer {

    public string Name {get;set}
    public string LastName {get;set}
    public string Email {get;set}

    public Customer(string name, string lastName, string email) {
        Name = name;
        LastName = lastName;
        Email = email;
    }
}

/* this class only cares about storing data in the database*/
public class CustomerRepository {

    private readonly string _repository;

    public CustomerRepository(string repository){
        _repository = repository;
    }

    public void Save(Customer customer){

        if(_repository == "SQLServer"){
            /* Saving in SQLServer*/
        }
        else if(_repository == "MongoDB"){
            /* Saving in MongoDB*/
        }
        else{
            throw new Exception("Database not supported");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Open-Closed Principle

Software entities (classes, modules, functions, etc.)
should be open for extension, but closed for
modification.

Violation

public class EmailNotifier {
    public void Notify(string message){
        /* Notify via e-mail */
    }
}

public class SMSNotifier  {
    public void Notify(string message){
        /* Notify via SMS */
    }
}

public class AlertManager {

    public void SendNotification(List<string> notifiers, string message){
        /* WARNING: New notifiers cannot be added without modifying this module */
        foreach(var notifier in notifiers){
            switch(notifier){
                case "Email": 
                    var email = new EmailNotifier();
                    email.Notify(message);
                case "SMS": 
                    var sms = new SMSNotifier();
                    sms.Notify(message); 
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Compliance

public class Notifier {
    public void Notify(string message){
        return;
    }
}

public class EmailNotifier : Notifier {
    public void Notify(string message){
        /* Notify via e-mail */
    }
}

public class SMSNotifier : Notifier {
    public void Notify(string message){
        /* Notify via SMS */
    }
}

public class WhatsAppNotifier : Notifier {
    public void Notify(string message){
        /* Notify via WhatsApp */
    }
}

public class AlertManager {

    public void SendNotification(List<Notifier> notifiers, string message){
        /* New notifiers can be added without modifying this module */
        foreach(var notifier in notifiers){
            notifier.Notify(message);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Liskov Substitution Principle

Functions that use pointers or references to base
classes must be able to use objects of derived classes
without knowing it.

Violation

public interface IDatabaseRepository {
    bool Connect(string connectionString);
    Table GetTable(string tableName);
}

public class OracleRepository : IDatabaseRepository{

    public bool Connect(string connectionString){
        /* Connect to the database */
    }

    public Table GetTable(string tableName){
        /* fetch table and return it*/
    }
}

public class SQLServerRepository : IDatabaseRepository{

    public bool Connect(string connectionString){
        /* Connect to the database */
    }

    public Table GetTable(string tableName){
        /* fetch table and return it*/
    }
}

/* Imagine MongoDB support was added later in the application: */
public class MongoDBRepository : IDatabaseRepository {

    public bool Connect(string connectionString){
        /* Connect to the database */
    }

    public Table GetTable(string tableName){
        /* MongoDB does not have Tables!! */
        throw new NotImplementedException();
    }
}

public class Client {

    public void GetTable(IDatabaseRepository repository, string tableName){   
        /* WARNING: If the MongoDBRepository object is passed as a parameter, calling GetTable() will break the application.
        And we cannot check here if the type is either OracleRepository or SQLServerRepository because that would violate the Open-Closed Principle */      
        repository.GetTable(tableName);
    }
}
Enter fullscreen mode Exit fullscreen mode

Compliance

public interface IDatabaseRepository {
    bool Connect(string connectionString);    
}

public interface IRelationalDatabaseRepository : IDatabaseRepository {
    Table GetTable(string connectionString);    
}

public class OracleRepository : IRelationalDatabaseRepository{

    public bool Connect(string connectionString){
        /* Connect to the database */
    }

    public Table GetTable(string tableName){
        /* fetch table and return it*/
    }
}

public class SQLServerRepository : IRelationalDatabaseRepository{

    public bool Connect(string connectionString){
        /* Connect to the database */
    }

    public Table GetTable(string tableName){
        /* fetch table and return it*/
    }
}

public class MongoDBRepository : IDatabaseRepository {

    public bool Connect(string connectionString){
        /* Connect to the database */
    }
}

public class Client {

    public void GetTable(IRelationalDatabaseRepository repository, string tableName){
        /* Now we are safe from exceptions, because every instance of IRelationalDatabaseRepository must handle the GetData method.*/
        repository.GetTable(tableName);
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Interface Segregation Principle

clients should not be forced to depend upon interfaces
that they do not use.

Violation

public interface IDatabaseRepository {
    Table GetTable(string tableName);
    /* Imagine MongoDB support was added later in the application: */
    Collection GetCollection(string collectionName);
}

public class SQLServerRepository : IDatabaseRepository {

    public Table GetTable(string tableName) {
        /* fetch table and return it*/
    }


    public Collection GetCollection(string collectionName) {
        /* 
           Method GetCollection is being forced on this class.
           SQLServer does not have Collections! 
        */
        throw new NotImplementedException();
    }
}

/* Imagine MongoDB support was added later in the application: */
public class MongoDBRepository : IDatabaseRepository {

    public Table GetTable(string tableName) {
        /* 
           Method GetTable is being forced on this class.
           MongoDB does not have Tables! 
        */
        throw new NotImplementedException();
    }

    public Collection GetCollection(string collectionName) {
        /* fetch collection and return it*/
    }
}
Enter fullscreen mode Exit fullscreen mode

Compliance

/* Split the IDatabaseRepository interface between IRelationalDatabaseRepository and INonRelationalDatabaseRepository so no implementation is forced to handle methods it doesn't need.*/
public interface IDatabaseRepository {

}

public interface IRelationalDatabaseRepository : IDatabaseRepository {
    Table GetTable(string tableName);
}

public interface INonRelationalDatabaseRepository : IDatabaseRepository {
    Collection GetCollection(string collectionName);
}

public class SQLServerRepository : IRelationalDatabaseRepository {

    public Table GetTable(string tableName) {
        /* fetch table and return it*/
    }
}

public class MongoDBRepository : INonRelationalDatabaseRepository{

    public Collection GetCollection(string collectionName) {
        /* fetch collection and return it*/
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Dependency Inversion Principle

a. high level modules should not depend upon low
level modules. both should depend upon abstractions.

b. abstractions should not depend upon details. details
should depend upon abstractions.

Violation

public class CustomerRepository {

    private readonly string _repository;

    public CustomerRepository(string repository){
        _repository = repository;
    }

    public void Save(Customer customer){

        /* WARNING: The high level module CustomerRepository DEPENDS on the low level modules SQLServerRepository */
        if(_repository == "SQLServer"){
            var repository = new SQLServerRepository();
            /* Saving in SQLServer*/
        }
        else{
            throw new Exception("Database not supported");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Compliance


public interface IDatabaseRepository{
    void Save(Customer customer);
}

public class SQLServerRepository : IDatabaseRepository {

    public void Save(Customer customer){
        /* Saving in SQLServer*/
    }
}

public class CustomerRepository {

    private readonly IDatabaseRepository _repository;

    public CustomerRepository(IDatabaseRepository repository){
        _repository = repository;
    }

    /* Now the high level module CustomerRepository and the low level modules SQLServerRepository and MongoDBRepository DEPEND on the abstraction IDatabaseRepository */
    public void Save(Customer customer){        
        _repository.Save(customer);
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)