SOLID Principles - gablesiak/learning-path GitHub Wiki

S – Single Responsibility

-class should only have one responsibility

-class should only have one reason to change

-amount of classes in project increases, but it makes it easier to read/maintain

-class with one responsibility will have far fewer test cases

-less functionality in a single class will have fewer dependencies

-atomic classes -> impossible to reduce

Wrong code example:

class Person
{
    public string Name { get; set; }
    public string Lastname { get; set; }
    public string City { get; set; }
    public string Street { get; set; }
    public int HouseNumber { get; set; }
    public string Email { get; set; }
    
    public Person(string name, string lastname, string email)
    {
        Name = name;
        Lastname = lastname;
        Email = ValidateEmail(email);
    }
    
    private string ValidateEmail(string email) 
    {
        if (!email.Contains("@") || !email.Contains("."))
        {
            throw new FormatException("Email address has a wrong format!");
        }
        
        return email;
    }
}

Errors:

-contains mail validation method, it shouldn't be a responsibility of this particular class

-it should not contain attributes that are not related to it - address

-problem with address validation in other places - it'd require to use class person

-it'd be necessary to copy the code in many places

Corrected version:

class Address
{
    public string City { get; set; }
    public string Street { get; set; }
    public int HouseNumber { get; set; }
}

class Person
{
    public string Name { get; set; }
    public string Lastname { get; set; }
    public string Email { get; set; }
    public Address PersonAddress { get; set; }
    
    public Person(string name, string lastname, string email)
    {
        Name = name;
        Lastname = lastname;
        Email = email;
    }
}

class EmailValidator
{
    public void ValidateEmail(string email)
    {
        if (!email.Contains("@") || !email.Contains("."))
        {
            throw new FormatException("Email address has a wrong format!");
        }
    }


-splited into 3 classes

-each class have single responsibility


O-Open/Closed

-classes should be open for extension but closed for modification

-it stops from modifying existing code and causing potential new issues

-exception - fixing bugs in existing code

Wrong code example:

class Square
{
    public int A { get; set; }
}

class Rectangle
{
    public int A { get; set; }
    public int B { get; set; }
}

class Calculator
{
    public int Area(object shape)
    {
        if (shape is Square)
        {
            Square square = (Square)shape;
            return square.A * square.A;
        }
        else if (shape is Rectangle)
        {
            Rectangle rectangle = (Rectangle)shape;
            return rectangle.A * rectangle.B;
        }
        
        return 0;
    }
}

Errors:

-adding a new shape => class modification

Corrected version:

abstract class Shape
{
    public abstract int Area();
}

class Square : Shape   
{
    public int A { get; set; }
    
    public override int Area()
    {
        return A * A;
    }
}

class Rectangle : Shape
{
    public int A { get; set; }
    public int B { get; set; }
    
    public override int Area()
    {
        return A * B;
    }
}

class Calculator
{
    public int Area(Shape shape)
    {
        return shape.Area();
    }
}

-Base class methods are used correctly for inheritance


L-Liskov substitution

-Plan and use inheritance so that derived classes can use all the methods from the base class.

-the rule is broken most often with too general polymorphism

-derived classes override the base class's methods, replacing its mismatched logic

Wrong code example:

public abstract class Bird
{
    public void Fly() { }       
}
     
public class Dove : Bird
{
    public void Fly() 
    { 
        // code
    }       
}
 
public class Penguin : Bird
{
    public void Fly() 
    { 
        // code
    }       
}


Errors:

-if we force the use of the base class, the inheriting classes will be affected with logical errors

Corrected version:

{
    public void Fly() { }       
}
 
public abstract class WalkingBird
{
    public void Walk() { }      
}
     
public class Dove : FlyingBird
{
    public void Fly() 
    { 
        // tresc metody w klasie gołąb
    }       
}
 
public class Penguin : WalkingBird
{
    public void Walk() 
    { 
        // tresc metody w klasie pingwin
    }       

-Base class methods are used correctly for inheritance


I-Interface Segregation Principle

-orderliness in interfaces

-maximum atomicity

-simplification of interface/base class

-more dedicated interfaces better than one general

-future issues with inheritance

-problems with methods that are later unnecessary in other code snippet

Wrong code example:

public interface IAccount
{
    public void CreateUser() { }
    public void EditUser() { }
    public void DeleteUser() { }
    public void ValidateLoginUser() { } 
}
     
public class Register : IAccount
{
    public void CreateUser() 
    { // tresc metody}
 
     public void EditUser()
     {  throw new NotImplementedException();}  
 
     public void DeleteUser()
     {  throw new NotImplementedException(); }  
 
     public bool ValidateLoginUser()
     {   // tresc metody }
}
 
public class UserProfile : IAccount
{
    public void CreateUser() 
    {  throw new NotImplementedException(); }
 
     public void EditUser()
     { // tresc metody }  
 
     public void DeleteUser()
     {  throw new NotImplementedException();}  
 
     public bool ValidateLoginUser()
     { throw new NotImplementedException();}  
}

Errors:

-IAccount contains 4 methods

-Classes Register i UserProfile inherit all methods from interface, not all of them are necessary

-this forces an addition of exception

Corrected version:

{
    public void CreateUser() { }        
}
 
public interface IEditU
{
        public void EditUser() { }
}
 
public interface IDeleteU
{
    public void DeleteUser() { }
}
 
public interface IValidateU
{
    public void ValidateLoginUser() { }
}
     
public class Register : ICreateU, IValidateU
{
    public void CreateUser() 
    { 
        // code
    }   
 
    public bool ValidateLoginUser()
    {
        // code
    }
}
 
public class UserProfile : IEditU
{
    public void EditUser() 
    { 
        // code
    }       
}

-inheritance added as needed

-smaller interfaces


D-Dependecy Inversion Principle

-all dependencies should depend as much as possible on abstraction and not on a specific type

-dependence, e.g. inheritance

-business logic classes should not be dependent on non-core classes

Wrong code example:

{
 
    public SMS sendSms { get; set; }
    public MAIL sendMail { get; set; }
     
     
    public Message()
    {
        sendSms = new SMS();
        sendMail = new MAIL();
    }
     
    public void SendMessages()
    {
        sendSMS.Send();
        sendMail.Send();
    }
}

Errors:

-class message creates 2 objects in constructor, based on SMS and mail

-the upper-level class depends on the lower-level classes

-sendmessage – methods from 2 related classes

Corrected version:

{
    public void Send();
}
 
public class SMS : IMessage
{
    public void Send()
    {
        // treść metody obsługująca SMS
    }
}
 
public class MAIL : IMessage
{
    public void Send()
    {
        // treść metody obsługująca MAIL
    }
}
 
public class Message
{
    private IEnumerable<IMessage> allMessages;
 
    public Messenger(IEnumerable<IMessage> messages)
    {
        allMessages = messages;
    }
 
    public void Send()
    {
         allMessages.AsEnumerable().ToList().ForEach(n => n.Send());
    }
}

-abstraction – interface defines send method

-dependencies between high and low level classes removed

⚠️ **GitHub.com Fallback** ⚠️