Posts Simple SOLID
Post
Cancel

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 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");
        }
    }
}

Compliance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 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");
        }
    }
}

2. Open-Closed Principle

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

Violation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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); 
            }
        }
    }
}

Compliance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
        }
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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);
    }
}

Compliance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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);
    }
}

4. Interface Segregation Principle

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

Violation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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*/
    }
}

Compliance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 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*/
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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");
        }
    }
}

Compliance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
    }
}
This post is licensed under CC BY 4.0 by the author.