Become SOLID in C#

Pavel Nasovich

Show me your code

public sealed class DelimitedSampleRecordLayout : DelimitedLayout<FixedSampleRecord>
{
    public DelimitedSampleRecordLayout()
    {
        this.WithDelimiter(";")
            .WithQuote("\"")
            .WithMember(x => x.Cuit)
            .WithMember(x => x.Nombre)
            .WithMember(x => x.Actividad, c => c.WithName("AnotherName"));
    }
}

Agenda

  1. What is SOLID?
  2. Single Responsibility Principle
  3. Open Closed Principle
  4. Liskov Substitution Principle
  5. Interface Segregation Principle
  6. Dependency Inversion Principle
  7. Questions and answers

Assumptions

  • Brains
  • Motivation
  • Basic C#/.net knowledge
  • Basic GIT knowledge
  • Bitbucket\Github

What will we do?

What will we do?

  • Discuss
  • Code
  • Code
  • Code
  • Homework

What will we do?

What should I do?

  • Clone git
  • Register in Telegram
  • Make changes
  • Give me rights for code review
  • ???
  • Profit!

What is SOLID?

What is SOLID?

S.O.L.I.D is an acronym for the first five object-oriented design(OOD) principles by Robert C. Martin, popularly known as Uncle Bob.

These principles, when combined together, make it easy for a programmer to develop software that are 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 the agile or adaptive software development.

What is SOLID?

S.O.L.I.D stands for:

When expanded the acronyms might seem complicated, but they are pretty simple to grasp.

S Single-responsiblity principle
O Open-closed principle
L Liskov substitution principle
I Interface segregation principle
D Dependency Inversion Principle

What is SOLID?

Honestly, S.O.L.I.D might seem to be a handful at first, but with continuous usage and adherence to its guidelines, it becomes a part of you and your code which can easily be extended, modified, tested, and refactored without any problems.

Single Responsibility Principle

Single Responsibility Principle

Название: Принцип единственной ответственности.

Определение: У класса/модуля должна быть лишь одна причина для изменения.

Смысл принципа: Борьба со сложностью, важность которой резко возрастает при развитии логики приложения.

Краткое описание: Любой сложный класс должен быть разбит на несколько простых составляющих, отвечающих за определенный аспект поведения, что упрощает как понимание, так и будущее развитие.

Single Responsibility Principle

Two points of change for your application.

Single Responsibility Principle

Have a look at the code below, can you guess what the problem is ?

class Customer
{
    public void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());
        }
    }
}

The above customer class is doing things WHICH HE IS NOT SUPPOSED TO DO. Customer class should do customer data validations, call the customer data access layer etc, but if you see the catch block closely it also doing LOGGING activity. 

In simple words its over loaded with lot of responsibility.

Single Responsibility Principle

But if we can have each of those items separated its simple, easy to maintain and one change does not affect the other.

The same principle also applies to classes and objects in software architecture.

Single Responsibility Principle

public class UserService
{
     public void Register(string email, string password)
     {
          if (!email.Contains("@"))
              throw new ValidationException("Email is not an email!");
 
         var user = new User(email, password);
         _database.Save(user);
 
         _smtpClient.Send(new MailMessage("mysite@nowhere.com", email){Subject="HEllo fool!"});
     }
}

The name Register suggests that the method should register the user in the system.

Doing email validation doesn’t seem to belong in an register method.

Let’s break it out into a new method.

Single Responsibility Principle

public class UserService
{
     public void Register(string email, string password)
     {
          if (!ValidateEmail(email))
              throw new ValidationException("Email is not an email!");
 
         var user = new User(email, password);
         _database.Save(user);
 
         _smtpClient.Send(new MailMessage("mysite@nowhere.com", email){Subject="HEllo fool!"});
     }
 
     public bool ValidateEmail(string email)
     {
         return email.Contains("@");
     }
}

If we continue to look at the method we’ll see that it also sends an email and is therefore also responsible of delivering the email.

Lets move that to a new method too.

Single Responsibility Principle

public class UserService
{
     public void Register(string email, string password)
     {
          if (!ValidateEmail(email))
              throw new ValidationException("Email is not an email!");
 
         var user = new User(email, password);
         _database.Save(user);
 
         SendEmail(new MailMessage("mysite@nowhere.com", email){Subject="HEllo fool!"});
     }
 
     public virtual bool ValidateEmail(string email)
     {
         return email.Contains("@");
     }
 
     public bool SendEmail(MailMessage message)
     {
         _smtpClient.Send(message);
     }
}

We did it again. A single line method. Is this sh!t really necessary?

Yep, it is.

Single Responsibility Principle

public class UserService
{
     EmailService _emailService;
 
     public UserService(EmailService emailService)
     {
         _emailService = emailService;
     }
     public void Register(string email, string password)
     {
          if (!_emailService.ValidateEmail(email))
              throw new ValidationException("Email is not an email!");
 
         var user = new User(email, password);
         _database.Save(user);
 
         emailService.SendEmail(new MailMessage("mysite@nowhere.com", email){Subject="HEllo fool!"});
     }
}
 
public class EmailService
{
     public bool virtual ValidateEmail(string email)
     {
         return email.Contains("@");
     }
 
     public bool SendEmail(MailMessage message)
     {
         _smtpClient.Send(message);
     }
}

Summary

  1. Document your code. Using AND’s should make you check your code again.
  2. You should be able to associate all method names with the class/interface name.
  3. A method should only contain logic that can be associated with the method name.

Single Responsibility Principle

Let's code

Open Closed Principle

Open Closed Principle

Open Closed Principle

Название: Принцип открытости/закрытости

Определение: Программные сущности (классы, модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации.

Смысл принципа: ограничить распространение изменений минимальным числом классов/модулей; позволить вести параллельную разработку путем фиксации интерфейсов классов и открытости реализаций.

Краткое описание: закрытость модулей означает стабильность интерфейса и возможность использования классов/модулей клиентами. Открытость модулей - это возможность внесения изменений в поведении, путем изменения реализации или же путем переопределения.

Open Closed Principle

public class Rectangle  
{
    public double Width { get; set; }
    public double Height { get; set; }
}

public class CombinedAreaCalculator  
{
    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;
            }
        }
        return area;
    }
}
0
00

Let's imagine a scenario in which we are given several Rectangles and need to calculate the total combined area of all of them. We then come along and create a solution that looks something like this:

Open Closed Principle

public class Circle  
{
    public double Radius { get; set; }
}
0
00

This code does exactly what we want it to do, and it works great for rectangles. But, what happens if some of our shapes are circles?

We have to change the CombinedAreaCalculator to accommodate this:

public class CombinedAreaCalculator {
    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;
            }
            if (shape is Circle) {
                Circle circle = (Circle)shape;
                area += (circle.Radius * circle.Radius) * Math.PI;
            }
        }
        return area;
    }
}

Open Closed Principle

0
00

By doing this we have violated the Open/Closed Principle; in order to extend the functionality of the CombinedAreaCalculator class, we had to modify the class's source.

What happens when some of our shapes are triangles, or octogons, or trapezoids? In each case, we have to add a new if clause to the CombinedAreaCalculator.

In essence, CombinedAreaCalculator is not closed for modification, and isn't really open for extension (what good would inheriting from CombinedAreaCalculator do for another class?).

Open Closed Principle

0
00

So, we need to refactor. There are many ways to refactor this to uphold Open/Closed; I'm going to show you just one of them. Let's create an abstract class that all the shapes can inherit from:

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

Notice that our abstract Shape class has a method for Area. We're moving the dependency for calculating the area from one centralized class to the individual shapes.

The CombinedAreaCalculator will just call each individual Shape class's Area method. 

Open Closed Principle

0
00

The individual shape classes can now be implemented like this:

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;
    }
}
public class Triangle : Shape  
{
    public double Height { get; set; }
    public double Width { get; set; }
    public override double Area() {
        return Height * Width * 0.5;
    }
}

Open Closed Principle

0
00

And finally, we can create the new CombinedAreaCalculator class:

public class CombinedAreaCalculator  
{
    public double Area(Shape[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.Area();
        }
        return area;
    }
}

We've made the Shape abstract class and the CombinedAreaCalculator class open for extension and closed for modification, thereby upholding the Open/Closed Principle.

If we need to add any other shapes, we just create a class for them that inherits from Shape and we're good to go.

Open Closed Principle

Open Closed Principle

Potential Hazards

You shouldn't interpret this rule as "don't change already implemented classes, ever." Of course scenarios will arise that will force or require you to change classes that are already implemented.

However, we should use discretion when attempting to make these modifications, and keeping OCP in mind allows us to do that in a more efficient manner.

We only need to keep in mind that modules, ideally, should be open for extension and closed for modification. But if we have to change the code to support new rules and requirements, and the best way to support those requirements is to change existing class functionality, we shouldn't be afraid to just do it.

Open Closed Principle

class Product
{
    public Product(ProductColor color)
    {
        this.Color = color;
    }

    public Product(ProductColor color, ProductSize size)
    {
        this.Color = color;
        this.Size = size;
    }

    public ProductColor Color { get; set; }
    public ProductSize Size { get; set; } 
}
public enum ProductColor
{
    Blue,
    Yellow,
    Red,
    Gold,
    Brown
}
public enum ProductSize
{
    Small,
    Medium,
    Large,
    ReallyBig
}
1
11

Open Closed Principle

class ProductFilter
{
	public IEnumerable<Product> ByColor(IList<Product> products, ProductColor productColor)
	{
		foreach (var product in products)
		{
			if (product.Color == productColor) yield return product;
		}
	}

	public IEnumerable<Product> ByColorAndSize(
            IList<Product> products, 
            ProductColor productColor,
            ProductSize productSize)
	{
		foreach (var product in products)
		{
			if ((product.Color == productColor) && (product.Size == productSize)) {
                            yield return product;
                        }
		}
	}

	public IEnumerable<Product> BySize(IList<Product> products, ProductSize productSize)
	{
		foreach (var product in products)
		{
			if ((product.Size == productSize)) yield return product;
		}
	}
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterByBlue_return_2()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.ByColor(products, ProductColor.Blue);

	// assert
	Assert.That(result.Count(), Is.EqualTo(2));
	Assert.That(result, Has.All.Matches<Product>(x => x.Color == ProductColor.Blue));
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterBySmall_return_2()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.BySize(products,  ProductSize.Small);

	// assert
	Assert.That(result.Count(), Is.EqualTo(2));
	Assert.That(result, Has.All.Matches<Product>(x => x.Size == ProductSize.Small));
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterByBlueAndSmall_return_1()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.ByColorAndSize(products, ProductColor.Blue, ProductSize.Small);

	// assert
	Assert.That(result.Count(), Is.EqualTo(1));
	Assert.That(result, Has.All.Matches<Product>(x => x.Size == ProductSize.Small));
}
1
11

Open Closed Principle

1
11

Open Closed Principle

class Product
{
    public Product(ProductColor color)
    {
        this.Color = color;
    }

    public Product(ProductColor color, ProductSize size)
    {
        this.Color = color;
        this.Size = size;
    }

    public ProductColor Color { get; set; }
    public ProductSize Size { get; set; } 
}
public enum ProductColor
{
    Blue,
    Yellow,
    Red,
    Gold,
    Brown
}
public enum ProductSize
{
    Small,
    Medium,
    Large,
    ReallyBig
}

After refactoring. Base structure is the same.

1
11

Open Closed Principle

class ProductFilter
{
	public IEnumerable<Product> By(
            IList<Product> products, 
            IFilterSpecification filterSpecification)
	{
		return filterSpecification.Filter(products);
	}
}
interface IFilterSpecification
{
	IEnumerable<Product> Filter(IList<Product> products);
}
1
11

Open Closed Principle

class FilterSpecificationColor : IFilterSpecification
{
	private readonly ProductColor productColor;

	public FilterSpecificationColor(ProductColor productColor)
	{
		this.productColor = productColor;
	}

	public IEnumerable<Product> Filter(IList<Product> products)
	{
		foreach (var product in products)
		{
			if (product.Color == productColor) yield return product;
		}
	}
}
1
11

Open Closed Principle

class FilterSpecificationSize : IFilterSpecification
{
	private readonly ProductSize productSize;

	public FilterSpecificationSize(ProductSize productSize)
	{
		this.productSize = productSize;
	}

	public IEnumerable<Product> Filter(IList<Product> products)
	{
		foreach (var product in products)
		{
			if (product.Size == productSize) yield return product;
		}
	}
}
1
11

Open Closed Principle

class FilterSpecificationColorAndSize : IFilterSpecification
{
	private readonly ProductColor productColor;
	private readonly ProductSize productSize;

	public FilterSpecificationColorAndSize(
            ProductColor productColor, 
            ProductSize productSize)
	{
		this.productColor = productColor;
		this.productSize = productSize;
	}

	public IEnumerable<Product> Filter(IList<Product> products)
	{
		foreach (var product in products)
		{
			if (product.Color == productColor 
                            && product.Size == productSize) 
                        {
                            yield return product;
                        }
		}
	}
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterByBlue_return_2()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.By(products, new FilterSpecificationColor(ProductColor.Blue));

	// assert
	Assert.That(result.Count(), Is.EqualTo(2));
	Assert.That(result, Has.All.Matches<Product>(x => x.Color == ProductColor.Blue));
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterBySmall_return_2()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.By(products, new FilterSpecificationSize(ProductSize.Small));

	// assert
	Assert.That(result.Count(), Is.EqualTo(2));
	Assert.That(result, Has.All.Matches<Product>(x => x.Size == ProductSize.Small));
}
1
11

Open Closed Principle

private IList<Product> BuildProducts()
{
	return new List<Product> {
		new Product(ProductColor.Blue, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Small),
		new Product(ProductColor.Yellow, ProductSize.Medium),
		new Product(ProductColor.Red, ProductSize.Large),
		new Product(ProductColor.Blue, ProductSize.ReallyBig)
	};
}
[Test]
public void filterByBlueAndSmall_return_1()
{
	// arrange
	ProductFilter filter = new ProductFilter();
	IList<Product> products = BuildProducts();

	// act
	var result = filter.By(products, 
            new FilterSpecificationColorAndSize(ProductColor.Blue, ProductSize.Small));

	// assert
	Assert.That(result.Count(), Is.EqualTo(1));
	Assert.That(result, Has.All.Matches<Product>(x => x.Size == ProductSize.Small));
}
1
11

Open Closed Principle

Open Closed Principle

public abstract class Beverage
{
	public string Description { get; set; }
	public abstract Decimal GetCost();
}

public class ExpressoWithMocha : Beverage
{
	public override decimal GetCost()
	{
		return 5.5m;
	}
}

public class ExpressoWithSteamedMilkAndMocha : Beverage
{
	public override decimal GetCost()
	{
		return 6.5m;
	}
}
2
22

Open Closed Principle

public abstract class Beverage
{
	public string Description { get; set; }
	public abstract Decimal GetCost();
}
public class Expresso : Beverage
{
	public override Decimal GetCost()
	{
		return 5m;
	}
}
public class WithMocha : Beverage
{
	private readonly Beverage beverage;

	public WithMocha(Beverage beverage)
	{
		this.beverage = beverage;
	}

	public override decimal GetCost()
	{
		return beverage.GetCost() + 0.5m;
	}
}
public class WithSteamedMilk : Beverage
{
	private readonly Beverage beverage;

	public WithSteamedMilk(Beverage beverage)
	{
		this.beverage = beverage;
	}

	public override decimal GetCost()
	{
		return beverage.GetCost() + 1;
	}
}
2
22

Open Closed Principle

Open Closed Principle

public class Beverage
{
	private Decimal cost;
	public string Description { get; set; }

	public Beverage(Decimal cost)
	{
		this.cost = cost;
	}

	public Decimal GetCost()
	{
		return cost;
	}
}
public class PriceList
{
	public decimal GetExpresso()
	{
		return 5m;
	}
	public decimal GetMocha()
	{
		return 0.5m;
	}
	public decimal GetSteamedMilk()
	{
		return 1m;
	}
}
public abstract class Beverage
{
	public string Description { get; set; }
	public abstract Decimal GetCost();
}
3
33

Open Closed Principle

public class BeverageBuilder
{
	private readonly PriceList priceList;
	private Beverage beverage;

	public BeverageBuilder(PriceList priceList)
	{
		this.priceList = priceList;
	}

	public BeverageBuilder CreateExpresso()
	{
		beverage = new Beverage(priceList.GetExpresso());
		return this;
	}
	public BeverageBuilder WithMocha()
	{
		beverage = new Beverage(beverage.GetCost() + priceList.GetMocha());
		return this;
	}
	public BeverageBuilder WithSteamedMilk()
	{
		beverage = new Beverage(beverage.GetCost() + priceList.GetSteamedMilk());
		return this;
	}
	public Beverage Build()
	{
		return beverage;
	}
}
3
33

Open Closed Principle

Open Closed Principle

public void Parse()
{
    StringReader reader = new StringReader(scriptTextToProcess);
    StringBuilder scope = new StringBuilder();
    string line = reader.ReadLine();
    while (line != null)
    {
        switch (line[0])
        {
            case '$':
                // Process the entire "line" as a variable, 
                // i.e. add it to a collection of KeyValuePair.
                AddToVariables(line);
                break;
            case '!':
                // Depending of what comes after the '!' character, 
                // process the entire "scope" and/or the command in "line".
                if (line == "!execute")
                    ExecuteScope(scope);
                else if (line.StartsWith("!custom_command"))
                    RunCustomCommand(line, scope);
                else if (line == "!single_line_directive")
                    ProcessDirective(line);
 
                scope = new StringBuilder();
                break;
 
            default:
                // No processing directive, i.e. add the "line" 
                // to the current scope.
                scope.Append(line);
                break;
        }
 
        line = reader.ReadLine();
    }
}
4
44

Lets create an interface which is used for each handler (for '$' and '!')

Open Closed Principle

public interface IMyHandler
{
    void Process(IProcessContext context, string line);
}

Notice that we include a context object. This is quite important. If we create a new parser called SuperCoolParser in the future we can let it create and pass a SuperAwesomeContext to all handlers.

New handlers which supports that context can use it while others stick with the basic implementation.

We comply with Liskovs Substitution Principle and doesn’t have to change the IMyHandler.Process
signature (and therefore keeping it closed for modification) when we add new features later on.

4
44

Open Closed Principle

public class Parser
{
    private Dictionary<char, IMyHandler> _handlers = new Dictionary<char, IMyHandler>();
    private IMyHandler _defaultHandler;
 
    public void Add(char controlCharacter, IMyHandler handler)
    {
        _handlers.Add(controlCharacter, handler);
    }
 
    private void Parse(TextReader reader)
    {
        StringBuilder scope = new StringBuilder();
        IProcessContext context = null; // create your context here.
 
        string line = reader.ReadLine();
        while (line != null)
        {
            IMyHandler handler = null;
            if (!_handlers.TryGetValue(line[0], out handler))
                handler = _defaultHandler;
 
            handler.Process(context, line);
 
 
            line = reader.ReadLine();
        }
    }
}

The parser itself is implemented as:

4
44

Open Closed Principle

case '!':
    // Depending of what comes after the '!' character,
    // process the entire "scope" and/or the command in "line".
    if (line == "!execute")
	ExecuteScope(scope);
    else if (line.StartsWith("!custom_command"))
	RunCustomCommand(line, scope);
    else if (line == "!single_line_directive")
	ProcessDirective(line);

    scope = new StringBuilder();
    break;

Go back to

A lot of if statements. That method likely have to be changed to add support for more features. Hence it do also violate the principle. Let’s refactor again.

public interface ICommandHandler
{
    void Handle(ICommandContext context, string commandName, string[] arguments);
}
4
44

Open Closed Principle

public class CommandService : IMyHandler
{
    public void Add(string commandName, ICommandHandler handler) 
    {
    }
 
    public void Handle(IProcessContext context, string line)
    {
       // first word on the line is the command, all other words are arguments.
       // split the string properly
 
       // then find the corrext command handler and invoke it.
       // take the result and add it to the `IProcessContext`
    }
}
4
44

That gives more flexibility for both handling the actual protocol and add more commands. you do not have to change anything to add more functionality.

The solution is therefore OK regarding Open/Closed and some other SOLID principles.

Liskov Substitution Principle

Liskov Substitution Principle

Название: Принцип замещения Барбары Лисков

Определение: Должна быть возможность вместо базового типа подставить любой его подтип.

Смысл: Реализуйте наследование подтипов правильно.

Краткое описание: для корректной реализации отношения «ЯВЛЯЕТСЯ», наследник может ослаблять предусловие и усиливать постусловие (требовать меньше и гарантировать больше), при этом инварианты базового класса должны выполняться наследником. При нарушении этих правил подстановка экземпляров наследника в метод, принимающий базовый класс будет приводить к непредсказуемым последствиям.

Liskov Substitution Principle

Liskov Substitution Principle

What Is This Principle?

The Liskov Substitution Principle(LSP), named for and originally defined by Barbara Liskov, states that we should be able to treat a child class as though it were the parent class.

Essentially this means that all derived classes should retain the functionality of their parent class and cannot replace any functionality the parent provides.

The LSP is very similar in principle to the Open/Closed Principle.

Liskov Substitution Principle

Benefits?

This principle aims to keep functionality intact. It's main purpose is to guarantee that objects lower in a relational hierarchy can be treated as though they are objects higher in the hierarchy.

Basically, any child class should be able to do anything the parent can do.

Liskov Substitution Principle

A Simple Example

We'll use the classic Circle-Ellipse problem to demonstrate this principle. Let's imagine that we need to find the area of any ellipse.

So, we create a class that represents an ellipse:

public class Ellipse  
{
    public double MajorAxis { get; set; }
    public double MinorAxis { get; set; }

    public virtual void SetMajorAxis(double majorAxis)
    {
        MajorAxis = majorAxis;
    }

    public virtual void SetMinorAxis(double minorAxis)
    {
        MinorAxis = minorAxis;
    }

    public virtual double Area()
    {
        return MajorAxis * MinorAxis * Math.PI;
    }
}

Liskov Substitution Principle

A Simple Example

We know from high school geometry that a circle is just a special case for an ellipse, so we create a Circle class that inherits from Ellipse, but SetMajorAxis sets both axes (because in a circle, the major and minor axes must always be the same, which is just the radius):

public class Circle : Ellipse  
{
    public override void SetMajorAxis(double majorAxis)
    {
        base.SetMajorAxis(majorAxis);
        this.MinorAxis = majorAxis; //In a cirle, each axis is identical
    }
}

See the problem now?

Liskov Substitution Principle

A Simple Example

If we set both axes, attempting to calculate the area gives the wrong result.

Circle circle = new Circle();  
circle.SetMajorAxis(5);  
circle.SetMinorAxis(4);  
var area = circle.Area(); //5*4 = 20, but we expected 5*5 = 25 

This is a violation of the Liskov Substitution Principle. However, the best way to refactor this code is not obvious, as there are quite a few possibilities.

Liskov Substitution Principle

A Simple Example

One solution might be to have Circle implement SetMinorAxis as well:

public class Circle : Ellipse  
{
    public override void SetMajorAxis(double majorAxis)
    {
        base.SetMajorAxis(majorAxis);
        this.MinorAxis = majorAxis; //In a cirle, each axis is identical
    }

    public override void SetMinorAxis(double minorAxis)
    {
        base.SetMinorAxis(minorAxis);
        this.MajorAxis = minorAxis;
    }

    public override double Area()
    {
        return base.Area();
    }
}

Liskov Substitution Principle

A Simple Example

Another solution, one with less code overall, might be to treat Circle as an entirely separate class:

public class Circle  
{
    public double Radius { get; set; }
    public void SetRadius(double radius)
    {
        this.Radius = radius;
    }

    public double Area()
    {
        return this.Radius * this.Radius * Math.PI;
    }
}

Liskov Substitution Principle

A Simple Example

Both solutions have their own drawbacks.

  • The first can be considered a hack, since we have two different methods on the Circle class that essentially do the same thing.
  • The second could be considered improper modeling, as we are treating Circle like a separate class even though it really is a special case of Ellipse.

Given the choice, though, I personally am more likely to choose the second solution rather than the first one, as I feel it provides a better overall model (and doesn't use any redundant code).

Liskov Substitution Principle

ElectricDuck Example

Let’s use the motivator image as inspiration and define the following classes:

public interface IDuck
{
   void Swim();
   // contract says that IsSwimming should be true if Swim has been called.
   bool IsSwimming { get; }
}
public class ElectricDuck : IDuck
{
   bool _isSwimming;

   public void Swim()
   {
      if (!IsTurnedOn)
        return;
      _isSwimming = true;
      //swim logic  

   }

   bool IsSwimming { get { return _isSwimming; } }
}
public class OrganicDuck : IDuck
{
   public void Swim()
   {
      //do something to swim
   }

   bool IsSwimming 
   { 
       get 
       { 
        /* return if the duck is swimming */ 
       }
   }
}

Liskov Substitution Principle

ElectricDuck Example

And the calling code:

void MakeDuckSwim(IDuck duck)
{
    duck.Swim();
}

As you can see, there are two examples of ducks. One organic duck and one electric duck. The electric duck can only swim if it's turned on.

This breaks the LSP principle since it must be turned on to be able to swim as the IsSwimming (which also is part of the contract) won't be set as in the base class.

Liskov Substitution Principle

ElectricDuck Example

You can of course solve it by doing something like this:

void MakeDuckSwim(IDuck duck)
{
    if (duck is ElectricDuck)
        ((ElectricDuck)duck).TurnOn();
    duck.Swim();
}

But that would break Open/Closed principle and has to be implemented everywhere (and thefore still generates unstable code).

Liskov Substitution Principle

ElectricDuck Example

The proper solution would be to automatically turn on the duck in the Swim method and by doing so make the electric duck behave exactly as defined by the IDuck interface

public class ElectricDuck : IDuck
{
   public void Swim()
   {
      if (!IsTurnedOn)
        TurnOnDuck();
 
      //swim logic  
   }
}

Liskov Substitution Principle

Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.

Liskov Substitution Principle

Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.

Liskov Substitution Principle

Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.

Liskov Substitution Principle

Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.

Liskov Substitution Principle

Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.

Liskov Substitution Principle

Visitor pattern Example

I actually like the Visitor pattern a lot as it can take care of large if-statement spaghetti and it is simpler to implement than what you'd think on existing code. Say we have the following context:

public class Context {

    public void DoStuff(string query) {

        // outcome no. 1
        if (query.Equals("Hello")) {
            Console.WriteLine("Hello world!");
        } 

        // outcome no. 2
        else if (query.Equals("Bye")) {
            Console.WriteLine("Good bye cruel world!");
        }

        // a change request may require another outcome...
    }
}

Liskov Substitution Principle

Visitor pattern Example

And usage will be look like:

// usage:
Context c = new Context();

c.DoStuff("Hello");
// prints "Hello world"

c.DoStuff("Bye");
// prints "Bye"

Liskov Substitution Principle

Visitor pattern Example

The outcomes of the if-statement can be translated into their own visitors as each is depending on some decision and some code to run. We can extract these like this:

public interface IVisitor {
    public bool CanDo(string query);
    public void DoStuff();
}

// outcome 1
public class HelloVisitor : IVisitor {
    public bool CanDo(string query) {
        return query.Equals("Hello");
    }
    public void doStuff() {
         Console.WriteLine("Hello World");
    }
}
// outcome 2
public class ByeVisitor : IVisitor {
    public bool CanDo(string query) {
        return query.Equals("Bye");
    }
    public void DoStuff() {
        Console.WriteLine("Good bye cruel world");
    }
}

Liskov Substitution Principle

Visitor pattern Example

At this point, if the programmer did not know about the Visitor pattern, he'd instead implement the Context class to check if it is of some certain type. Because the Visitor classes have a boolean canDo method, the implementor can use that method call to determine if it is the right object to do the job.

The context class can use all visitors (and add new ones) like this:

Liskov Substitution Principle

Visitor pattern Example

public class Context {
    private List<IVisitor> visitors = new List<IVisitor>();

    public Context() {
        visitors.Add(new HelloVisitor());
        visitors.Add(new ByeVisitor());
    }

    // instead of if-statements, go through all visitors
    // and use the canDo method to determine if the 
    // visitor object is the right one to "visit"
    public void DoStuff(string query) {
        foreach(var visitor in visitors) {
            if (visitor.CanDo(query)) {
                visitor.DoStuff();
                break;
                // or return... it depends if you have logic 
                // after this foreach loop
            }
        }
    }

    // dynamically adds new visitors
    public void AddVisitor(IVisitor visitor) {
        if (visitor != null)
            visitors.add(visitor);
    }
}

Liskov Substitution Principle

Ostrich Example

Let's imagine that we want to create an object model of the birds. All looks rather good from the first view:

class Bird
{
   public virtual void Fly()
   {
      Console.WriteLine("Flying ....");
   }
}

class Pigeon: Bird
{
   public override void Fly()
  {
      //implementing some flying abilities
      base.Fly();
      Console.WriteLine("...and shitting on your head wherever you go ;)");
   }
}

Liskov Substitution Principle

Ostrich Example

And we use them as:

class Program
{
   static void Main()
   {
       var birds = new List<Bird>();        
       birds.Add(new Pigeon());
       birds.Add(new Ostrich());
        
       foreach (Bird bird in birds)        
       {
           bird.Fly();
       }
   }
}

Liskov Substitution Principle

Ostrich Example

New specification.

And we add...

Ostrich:

class Ostrich : Bird
{
   public override void Fly()
   {
      //Oops....
     Console.WriteLine("I wish i could but ....");
   }
}

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

  1. Install the Code Contracts for .NET extension into Visual Studio.
  2. Open Visual Studio and load the solution containing the projects you want to apply contracts to.
  3. Open the properties for the project and you’ll see a new tab in the project properties window called “Code Contracts”
  4. Make sure that the “Perform Runtime Contract Checking” and “Perform Static Contract Checking” boxes are checked. For the moment the other options can be left at their default values. Only apply these to the debug build. It will slow down the application while it is running as each time a method with contract conditions is called it will be performing runtime checks.

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

Setting up the Contract

Using the Rectangle/Square example from Bob Martin’s book Agile Principles, Patterns and Practices in C# here is the code with contracts added:

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

public class Rectangle {
    private int _width;
    private int _height;
    public virtual int Width {
        get { return _width; }
        set {
            Contract.Requires(value >= 0);
            Contract.Ensures(Width == value);
            Contract.Ensures(Height == Contract.OldValue(Height));
            _width = value;
        }
    }

    public virtual int Height {
        get { return _height; }
        set {
            Contract.Requires(value >= 0);
            Contract.Ensures(Height == value);
            Contract.Ensures(Width == Contract.OldValue(Width));
            _height = value;
        }
    }
    public int Area { get { return Width * Height; } }
}

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

The Square class is in violation of the LSP because it changes the behaviour of the Width and Height setters. To any user of Rectangle that doesn’t know about squares it is quite understandable that they’d assume that setting the Height left the Width alone and vice versa.
So if they were given a square and they attempted to set the width and height to different values then they’d get a result from Area that was inconsistent with their expectation and if they set Height then queried Width they may be somewhat surprised at the result.

[Test]
public void TestSquare()
{
    var r = new Square();
    r.Width = 10;

    Assert.AreEqual(10, r.Height);
}

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

When the test runner gets to this test it will fail. Not because the underlying code is wrong (it will set Height to be equal to Width), but because the method violates the constraints of the base class.

Liskov Substitution Principle

Using Contracts to discover Liskov Substitution Principle Violations

The contract says that when the base class changes the Width the Height remains the same and vice versa.

So, despite the fact that the unit tests were not explicitly testing for an LSP violation in Square, the contract system sprung up and highlighted the issue causing the test to fail.

Liskov Substitution Principle

Violating one principle but following other

There are ways to break one of the principles but still have the other be followed. The examples below seem contrived, for good reason, but I've actually seen these popping up in production code (and even worser).

Follows OCP but not LSP

Lets say we have the given code:

public interface IPerson {}

public class Boss : IPerson {
    public void DoBossStuff() { ... }
}

public class Peon : IPerson {
    public void DoPeonStuff() { ... }
}

public class Context {
    public IEnumerable<IPerson> GetPersons { ... }
}

Liskov Substitution Principle

Violating one principle but following other

Follows OCP but not LSP

This piece of code follows the open-closed principle. If we're calling the context's GetPersons method, we'll get a bunch of persons all with their own implementations. That means that IPerson is closed for modification, but open for extension. However things take a dark turn when we have to use it:

// in some routine that needs to do stuff with 
// a collection of IPerson:
Innumerable<IPerson> persons = context.GetPersons();
foreach (IPerson person in persons) {
    // now we have to check the type... :-P
    if (person.GetType() == typeof(Boss)) {
        ((Boss) person).DoBossStuff();
    }
    else if (person.GetType() == typeof(Peon)) {
        ((Peon) person).DoPeonStuff();
    }
}

Liskov Substitution Principle

Violating one principle but following other

Follows OCP but not LSP

You have to do type checking and type conversion! Remember how I mentioned above how type checking is a bad thing? Oh no! But fear not, as also mentioned above either do some pull-up refactoring or implement a Visitor pattern. In this case we can simply do a pull up refactoring after adding a general method:

public class Boss : IPerson {
    // we're adding this general method
    public void DoStuff() {
        // that does the call instead
        this.doBossStuff();
    }
    public void DoBossStuff() { ... }
}

public interface IPerson {
    // pulled up method from Boss
    public void DoStuff();
}

Liskov Substitution Principle

Violating one principle but following other

Follows OCP but not LSP

The benefit now is that you don't need to know the exact type anymore, following LSP:

// in some routine that needs to do stuff with 
// a collection of IPerson:
Innumerable<IPerson> persons = context.GetPersons();
foreach (IPerson person in persons) {
    // yay, no type checking!
    person.DoStuff();
}

Liskov Substitution Principle

Violating one principle but following other

Follows LSP but not OCP

Lets look at some code that follows LSP but not OCP, it is kind of contrived but bear with me on this one it's very subtle mistake:

public class LiskovBase {
    public void doStuff() {
        Console.WriteLine("My name is Liskov");
    }
}
public class LiskovSub : LiskovBase {
    public void doStuff() {
        Console.WriteLine("I'm a sub Liskov!");
    }
}
public class Context {
    private LiskovBase base;

    // the good stuff
    public void DoLiskovyStuff() {
        base.DoStuff();
    }

    public void SetBase(LiskovBase base) { this.base = base }
}

Liskov Substitution Principle

Violating one principle but following other

Follows LSP but not OCP

The code does LSP because the context can use LiskovBase without knowing the actual type. You'd think this code follows OCP as well but look closely, is the class really closed? What if the DoStuff method did more than just print out a line?

The answer if it follows OCP is simply: NO, it isn't because in this object design we're required to override the code completely with something else. This opens up the cut-and-paste can of worms as you have to copy code over from the base class to get things working. The DoStuff method sure is open for extension, but it wasn't completely closed for modification.

Liskov Substitution Principle

Violating one principle but following other

Follows LSP but not OCP

We can apply the Template method pattern on this. The template method pattern is so common in frameworks that you might have been using it without knowing it (e.g. java swing components, c# forms and components, etc.).

Liskov Substitution Principle

Violating one principle but following other

Follows LSP but not OCP

public class LiskovBase {
    // this is now a template method
    // the code that was duplicated
    public void DoStuff() {
        Console.WriteLine(GetStuffString());
    }

    // extension point, the code that "varies"
    // in LiskovBase and it's subclasses
    // called by the template method above
    // we expect it to be virtual and overridden
    public virtual string GetStuffString() {
        return "My name is Liskov";
    }
}

public class LiskovSub : LiskovBase {
    // the extension overridden
    // the actual code that varied
    public override string GetStuffString() {
        return "I'm sub Liskov!";
    }
}

Liskov Substitution Principle

Violating one principle but following other

Follows LSP but not OCP

This example follows OCP and seems silly, which it is, but imagine this scaled up with more code to handle.

I keep seeing code deployed in production where subclasses completely override everything and the overridden code is mostly cut-n-pasted between implementations.

It works, but as with all code duplication is also a set-up for maintenance nightmares.

Liskov Substitution Principle

Conclusion

I hope this all clears out some questions regarding OCP and LSP and the differences/similarities between them. It is easy to dismiss them as the same but the examples above should show that they aren't.

Do note that, gathering from above sample code:

OCP is about locking the working code down but still keep it open somehow with some kind of extension points. This is to avoid code duplication by encapsulating the code that changes as with the example of Template Method pattern. It also allows for failing fast as breaking changes are painful (i.e. change one place, break it everywhere else). For the sake of maintenance the concept of encapsulating change is a good thing, because changes always happen.

Liskov Substitution Principle

Conclusion

I hope this all clears out some questions regarding OCP and LSP and the differences/similarities between them. It is easy to dismiss them as the same but the examples above should show that they aren't.

Do note that, gathering from above sample code:

LSP is about letting the user handle different objects that implement a supertype without checking what the actual type they are. This is inherently what polymorphism is about. This principle provides an alternative to do type-checking and type-conversion, that can get out of hand as the number of types grow, and can be achieved through pull-up refactoring or applying patterns such as Visitor.

Interface Segregation Principle

Interface Segregation Principle

Название: Принцип разделения интерфейсов

Определение: клиенты не должны вынужденно зависеть от методов, которыми не пользуются.

Смысл: класс должен предоставлять удобный интерфейс с точки зрения его разнообразных клиентов.

Краткое описание: интерфейс класса должен быть цельным и согласованным не зависимо от числа клиентов. Несколько разных клиентов вполне могут использовать лишь подмножество методов класса, до тех пор, пока интерфейс класса будет оставаться согласованным. Проблемы появляются тогда, когда интерфейс класса начинает распухать или появляются разные методы с похожей семантикой лишь для того, чтобы ими было удобно пользоваться определенным клиентам.

Interface Segregation Principle

Interface Segregation Principle

What Is This Principle?

The Interface Segregation Principle states that no client code object should be forced to depend on methods it does not use. Basically, each code object should only implement what it needs, and not be required to implement anything else.

Interface Segregation Principle

Benefits?

The ISP is all about reducing code objects down to their smallest possible implementation, and removing dependencies the object doesn't need to function properly. The result of implementing this principle, generally speaking, is to have a lot of small, focused interfaces that define only what is needed by their implementations.

Interface Segregation Principle

A Simple Example

Let's say we are tasked with modeling a newly-opened clothing store that sells jeans. At the moment, that's all they will sell. Now we, being smart programmers, think that we should model this so that if the store wants to sell different products in the future, we can model them appropriately. So, we create an interface for the products and a class for Jeans:

public interface IProduct  
{
    int ID { get; set; }
    double Weight { get; set; }
    int Stock { get; set; }
    int Inseam { get; set; }
    int WaistSize { get; set; }
}
public class Jeans : IProduct  
{
    public int ID { get; set; }
    public double Weight { get; set; }
    public int Stock { get; set; }
    public int Inseam { get; set; }
    public int WaistSize { get; set; }
}

Interface Segregation Principle

A Simple Example

Just a week later, the owner of the store comes to us and tells us that they're now going to sell baseball caps. So, wanting to use the interface we already created, we implement BaseballCap:

public class BaseballCap : IProduct  
{
    public int ID { get; set; }
    public double Weight { get; set; }
    public int Stock { get; set; }
    public int Inseam { get; set; }
    public int WaistSize { get; set; }
    public int HatSize { get; set; }
}

But wait! Why does a baseball cap have an inseam or waist size? Those properties don't make sense for a baseball cap, but because they were defined in IProduct, BaseballCap must implement them.

Interface Segregation Principle

A Simple Example

So what's the solution? Refactor! But how?

Well, what properties do both BaseballCap and Jeans need? Those properties can become the new IProduce interface:

public class Jeans : IProduct  
{
    public int ID { get; set; }
    public double Weight { get; set; }
    public int Stock { get; set; }
}

Interface Segregation Principle

A Simple Example

We currently sell jeans, but Inseam and WaistSize can apply to any type of pants, so let's create an IPants interface:

public interface IPants  
{
    public int Inseam { get; set; }
    public int WaistSize { get; set; }
}

We should be willing to bet that baseball caps won't be the only kinds of hats we'll sell, so we also make a focused IHat interface:

public interface IHat  
{
    public int HatSize { get; set; }
}

Interface Segregation Principle

A Simple Example

Now we can implement both Jeans and BaseballCap:

public class Jeans : IProduct, IPants  
{
    public int ID { get; set; }
    public double Weight { get; set; }
    public int Stock { get; set; }
    public int Inseam { get; set; }
    public int WaistSize { get; set; }
}

public class BaseballCap : IProduct, IHat  
{
    public int ID { get; set; }
    public double Weight { get; set; }
    public int Stock { get; set; }
    public int HatSize { get; set; }
}

Each class now has only properties that they need. Now we are upholding the Interface Segregation Principle!

Interface Segregation Principle

A Simple Example

Potential Hazards

As you might have guessed from the example, the ISP can potentially result in a lot of additional interfaces. If the store was to start selling t-shirts, for example, we would probably create another interface IShirt. There is a possibility that we will have a LOT of interfaces if we strictly adhere to this rule.

There's another potential hazard with implementing this principle: we could have interfaces that are only ever used for one implementation. For example, if we create that IShirt interface and the store only ever sells t-shirts, did we really need the interface? I'd argue that such interfaces are code bloat and should be removed until such time as they are needed, but many people disagree with me.

Interface Segregation Principle

MembershipProvider Example

The MembershipProvider in ASP.NET is a classical example of a violation. MSDN contains a large article (which 4 out of 34 have found useful) which contains a long and detailed instruction on how to properly implement the class.

Interface Segregation Principle

MembershipProvider Example

Solution

The provider could have been divided in several parts:

  • MembershipProvider – A facade to the below interfaces
  • IAccountRepository – Used to fetch/load accounts
  • IPasswordValidator – Checks that the password is valid (according to business rules)
  • IPasswordStrategy – How to store PW (hash it, use encryption or just store it in plain text)

Now you only have to implement a small part if you need to customize the provider (for instance IAccountRepository if you are using a custom data source). There’s a saying: “Favor composition over inheritance” which the membership providers illustrates well. The original solution used inheritance while mine used composition.

Interface Segregation Principle

IPersist Example

For example, you might have a class that implements an interface called IPersist. Let’s call it ReservationDatabase, and this class has code that persists a hotel reservation details to a database. IPersist contains the public method declaration of the method in ReservationDatabase that performs the persistence to the database. IPersist looks like this:

public interface IPersist
{
    bool Save(ReservationDetails reservation);
}

As you can see, this interface is very simple and it only has one responsibility. You could implement this from several classes that have a public method labeled “Save” to save some data to a database, to a XML document, to other data repository, etc… Everything is good so far.

Interface Segregation Principle

IPersist Example

Now, let’s say that you have a new class for logging some of the tasks related to the reservation, and you would like this ReservationLog class to implement IPersist as well as one of the functions of ReservationLog is to save this to a log repository. To implement IPersist you simply add the following at the top of your class:

public class ReservationLog : IPersist

Interface Segregation Principle

IPersist Example

This is the entire ReservationLog class that now implements IPersist:

public class ReservationLog : IPersist
 {
    public bool Save(ReservationDetails reservation)
    {
       // imagine some code here to perist data to database...
       return true;
    }

    public void Log(ReservationDetails reservation)
    {
       // imagine some code here to log to a log repository
    }

    public void SendNotification(ReservationDetails reservation)
    {
       // imagine some code here that notifies about logging
    }
 }

Interface Segregation Principle

IPersist Example

Now, since we are already implementing IPersist in our ReservationLog class which has two other methods, we update IPersist so it includes the two other methods from ReservationLog because it will be easier for the class or classes consuming IPersist to access these methods when logging is needed. So the updated IPersist interface looks like this now:

public interface IPersist
 {
    bool Save(ReservationDetails reservation);
    void Log(ReservationDetails reservation);
    void SendNotification(ReservationDetails reservation);
 }

Interface Segregation Principle

IPersist Example

What we did above might not look like a big change but it is. What we did above is wrong if you didn’t first carefully consider the consequences of implementing IPersist in a class that has multiple public methods and then adding those new method declarations to IPersist.

The main responsibility of IPersist was until now to abstract the details from classes that allowed to save reservation details to some data repository.

By implementing IPersist on ReservationLog and adding all of its public method declarations to this interface, the result is broken code in all of the classes that are implementing IPersist IF they don’t have public methods that match with those in the IPersist interface.

Interface Segregation Principle

IPersist Example

For example, the original ReservationDatabase class looks like this:

public class ReservationDatabase : IPersist
 {
    public bool Save(ReservationDetails reservation)
    {
       // imagine some code here to persist data to database...
       return true;
    }
 }

This code won’t compile anymore as this class and perhaps other classes in our code that implement IPersist, do not have methods that implement the two new declarations we added to IPersist:

void Log(ReservationDetails reservation);
void SendNotification(ReservationDetails reservation);

Interface Segregation Principle

IPersist Example

The common and not recommended “fix” for this is to “implement” these methods in all classes that implement IPersist and then just return a not implemented exception if anyone ever uses it. Using our example, our ReservationDatabase will look like this if we apply the quick fix:

public class ReservationDatabase : IPersist
 {
    public bool Save(ReservationDetails reservation)
    {
       // imagine some code here to persist data to database...
       return true;
    }
    public void Log(ReservationDetails reservation)
    {
       throw new NotImplementedException();
    }
    public void SendNotification(ReservationDetails reservation)
    {
       throw new NotImplementedException();
    }
 }

Interface Segregation Principle

IPersist Example

The code above is a clear example of breaking the interface segregation principle, which advises not to force any client to depend on methods that it does not use… like the two methods in our illustration above.

If you ever see code like this in your projects, where the methods are implementing an interface and all its methods and just throwing NotImplementedException errors on some of them, the code is breaking the interface segregation principle.

It is bad as the fix is quite simple and by fixing it you will end up with more maintainable code that can be easier to read and debug.

Interface Segregation Principle

IPersist Example

How to avoid violating the ISP principle?

It is actually very easy, it does require a bit more work but in the end you will have better organized code, you will be following this principle and a few more such as single responsibility and the universal KISS principle.

If you wanted to implement IPersist in your ReservationLog class you can still do it. However, do not update IPersist to accomodate for the other public methods in your ReservationLog class, instead create a new interface, implement it and use it to add any log related methods from your class to it. 

Interface Segregation Principle

IPersist Example

How to avoid violating the ISP principle?

This is what this new interface might look like:

public interface ILog
{
   void Log(ReservationDetails reservation);
   void SendNotification(ReservationDetails reservation);
}

Now you can implement ILog in your ReservationLog class. You can do this by adding a comma and typing the name of it after IPersist which was implemented earlier.

public class ReservationLog : IPersist, ILog

Interface Segregation Principle

IPersist Example

How to avoid violating the ISP principle?

That’s it! by doing this you can still implement IPersist without having to change other classes that might implement it and you also create a new interface that can be used by any classes wanting to implement the Log and SendNotification methods.

You could probably remove the SendNotification method and abstract it to yet another interface named INotify to keep your code simple, organized and maintainable.

Interface Segregation Principle

Repository Example

Abstractions are typically defined using interfaces. The IProductRepository could for example look like this:

public interface IProductRepository
{
    void Add(Product product);

    void Update(Product updatedProduct);

    void Remove(Product product);

    IList<Product> GetAll();

    Product Get(Guid id);
}

Interface Segregation Principle

Repository Example

However, as our application is likely to deal with other entities than products (for example customers and orders), it would make sense to generalize this interface using C# generics:

public interface IRepository<E> where E : IEntity
{
    void Add(E entity);

    void Update(E updatedEntity);

    void Remove(E entity);

    IList<E> GetAll();

    E Get(Guid id);
}

Interface Segregation Principle

Repository Example

IEntity is a simple interface ensuring that an entity always has a unique ID and a name:

public interface IEntity
{
    Guid Id { get; }

    string Name { get; }
}

This basically means that interfaces preferably should be as small and specific as possible. Actually, an interface with a single method can be a very good interface.

Interface Segregation Principle

Repository Example

You might have some entity services for which you do not want to expose full CRUD functionality – a read-only entity service, so to speak. For this purpose, it would make sense to define a specific IReadOnlyRepository interface:

public interface IReadOnlyRepository<E> where E : IEntity
{
    IList<E> GetAll();

    E Get(Guid id);
}

Interface Segregation Principle

Repository Example

Then the IRepository interface could be simplified to an extension of the IReadOnlyRepository interface:

public interface IRepository<E> : IReadOnlyRepository<E> 
    where E : IEntity
{
    void Add(E entity);

    void Update(E updatedEntity);

    void Remove(E entity);
}

Interface Segregation Principle

IOrder Example

Say for example when a thick interface is defined declaring a wide responsibility of members then there will be opportunities where some clients may have to implement members, which they don’t even use. In the below mentioned example ISP is violated where ProcessCreditCard method is not required by InpersonOrder class but is forced to implement.:

    public interface IOrder
    {
        void Purchase();
        void ProcessCreditCard();
    }

Interface Segregation Principle

IOrder Example

    public class OnlineOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //process through credit card
        }
    }
 
    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //Not required for inperson purchase
            throw new NotImplementedException();
        }
    }

Interface Segregation Principle

IOrder Example

Now let us fix the violation by breaking down the IOrder interface:

    public interface IOrder
    {
        void Purchase();
    }

    public interface IOnlineOrder
    {
        void ProcessCreditCard();
    }
    public class OnlineOrder : IOrder, IOnlineOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //process through credit card
        }
    }
    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
    }

Interface Segregation Principle

Существует несколько причин для выделения у класса дополнительных интерфейсов.

Во-первых, может появиться необходимость выделить интерфейс для класса целиком. Это бывает необходимым для «изменчивых» зависимостей (классов, взаимодействующих с внешним миром) или для стратегий (когда нам нужно полиморфное поведение, определенное интерфейсом).

Во-вторых, мы можем выделить некий аспект текущего класса, который покрывает не весь функционал, а лишь его часть, т.е. его отдельный аспект. Примером может служить добавление классу «ролевых» интерфейсов (Role Interface), типа ICloneable, IComparable<T>, IEquatable<T> и т.п. При этом выделение таких интерфейсов обычно требуется для использования класса в новом контексте (класс Person реализуется IComparable<T> для корректного использования в SortedList).

Interface Segregation Principle

В-третьих, может потребоваться выделение специализированных бизнес-интерфейсов, когда станет очевидным, что наш класс используется в нескольких разных контекстах. Например, когда репозиторий используется разными клиентами двумя способами: одна группа клиентов использует лишь операции для чтения данных, а другая группа – для обновления. Обычно это говорит не столько о нарушении ISP, сколько о наличии скрытой абстракции (IRepositoryReader и IRepositoryWriter).

В-четвертых, нам может потребоваться использовать несколько разных классов из разных иерархий наследования полиморфным образом. При этом обычно такое выделение происходит во время рефакторинга, когда необходимость в этом становится очевидной:

Interface Segregation Principle

«Ага, вот у нас есть два класса, которые мы могли бы использовать совместно, но они уже унаследованы от разных базовых классов. Давайте выделим для каждого из них единый интерфейс и избавимся от богомерзких if-else».

Interface Segregation Principle

TL;DR

  • Do not let client code use large classes which expose them to risk.
  • Split these classes into smaller classes (Single Responsibility Principle), or Interfaces.
  • Keep class interaction lean and focused.
  • Let interface design be dictated by the consuming clients, or by a logical service a subset of methods provides (e.g. Logging, UIConfirmation).
  • Interface Segregation does not necessarily mean using the Interface keyword, but it is designed for the purpose!
  • Automatically creating an Interface every time you create a class or method will lead to trouble.
  • An interface can also be too big – and may require segregating further.

Dependency Inversion Principle

Dependency Inversion Principle

Название: Принцип инверсии зависимостей

Определение: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций.

Смысл: сделать ключевые и/или изменчивые зависимости класса явными.

Краткое описание: слишком большое число зависимостей класса говорит о проблемах в дизайне. Возможно класс делает слишком многое, или же текущий класс не удачен, что приводит к необходимости дергания по одному методу у слишком большого числа зависимостей.

Dependency Inversion Principle

Dependency Inversion Principle

A Simple Example

Let's imagine that we are building an notifications client (a trite example, I know, but bear with me). We want to be able send both email and SMS text notifications. Here are some sample classes:

public class Email  
{
    public string ToAddress { get; set; }
    public string Subject { get; set; }
    public string Content { get; set; }
    public void SendEmail()
    {
        //Send email
    }
}

public class SMS  
{
    public string PhoneNumber { get; set; }
    public string Message { get; set; }
    public void SendSMS()
    {
        //Send sms
    }
}
public class Notification  
{
    private Email _email;
    private SMS _sms;
    public Notification()
    {
        _email = new Email();
        _sms = new SMS();
    }

    public void Send()
    {
        _email.SendEmail();
        _sms.SendSMS();
    }
}

Dependency Inversion Principle

A Simple Example

Notice that the Notification class, a higher-level class, has a dependency on both the Email class and the SMS class, which are lower-level classes. In other words, Notification is depending on the concrete implementation of both Email and SMS, not an abstraction of said implementation. Since DIP wants us to have both high and low-level classes depend on abstractions, we are currently violating this principle.

(One trick you can use to determine how tightly coupled your code is is to look for the new keyword. Generally speaking, the more instances of new keyword you have, the more tightly coupled your code is.)

Dependency Inversion Principle

A Simple Example

So, since all the SOLID principles are about reducing dependencies, how can we refactor this code to remove the dependency between Notification and Email? We need to introduce an abstraction, one that Notification can rely on and that Email and SMS can implement. Let's call that IMessage.

public interface IMessage  
{
    void SendMessage();
}
public class Email : IMessage  
{
    public string ToAddress { get; set; }
    public string Subject { get; set; }
    public string Content { get; set; }
    public void SendMessage()
    {
        //Send email
    }
}
public class SMS : IMessage  
{
    public string PhoneNumber { get; set; }
    public string Message { get; set; }
    public void SendMessage()
    {
        //Send sms
    }
}

Dependency Inversion Principle

A Simple Example

And, finally, we can make Notification depend on the abstraction IMessage rather than its concrete implementations:

public class Notification  
{
    private ICollection<IMessage> _messages;

    public Notification(ICollection<IMessage> messages)
    {
        this._messages = messages;
    }
    public void Send()
    {
        foreach(var message in _messages)
        {
            message.SendMessage();
        }
    }
}

With this refactoring, all Notification cares about is that there's an abstraction (the interface IMessage) that can actually send the notification, so it just calls that and calls it a day.

Dependency Inversion Principle

A Simple Example

In short, we have allowed both high-level and low-level classes to rely on abstractions, thereby upholding the Dependency Inversion Principle.

Potential Hazards

We cannot just implement a bunch of interfaces and call that DIP. Creating code just for the sake of having it leads to unnecessary complexity, the mortal enemy of maintainability. But we can use those interfaces to implement the necessary contracts the high-level objects need to call.

The key word there is necessary. As with all other coding, we should only implement code that is necessary and provides a benefit to the application.

Dependency Inversion Principle

Reader Example

Lets understand it through example.

The Copy class defined in above class diagram reads user input from KeyboardReader class and sends it to Printer using PrinterWritter class. As the diagram suggests high level component Copy class depends on two low level components KeyboardReader, PrinterWritter.

Since Copy class directly uses KeyboardReader and PrinterWritter classes, so if we want to redirect the output to some other output device or file system for e.g. FileStream we have to change Copy class implementation, which is not ideal.

Dependency Inversion Principle

Reader Example

Lets understand it through example.

public class KeyboardReader
{
    public string Read()
    {
        return Console.ReadLine();
    }
}

public class PrinterWritter
{
    public void Write(string output) {
        Console.WriteLine(output);
    }
}

public class Copy
{
    private KeyboardReader reader;
    private PrinterWritter writter;

    public void DoWork() {
        this.reader = new KeyboardReader();
        this.writter = new PrinterWritter();
        this.writter.Write(this.reader.Read());
    }
}
public class Program
{
    static void Main(string[] args)
    {
        Copy badCopy = new Copy();
        badCopy.DoWork();
        Console.ReadLine();
    }
}

Dependency Inversion Principle

Reader Example

DIP helps us to solve this issue by defining an abstraction layer between high level module and low level modules. Below diagram shows this abstraction layer with the help of Interfaces IReader and IWritter. Note that, Copy class depends on abstraction i.e. IReader and IWritter interfaces. KeyboardReader implements IReader interface and PrinterWritter and FileWritter implements IWritter interface. So this design adheres to both the points mentioned in DIP.

Dependency Inversion Principle

Reader Example

Below code example demonstrates DIP implementation

public interface IReader
{
    string Read();
}

public class KeyboardReader : IReader
{
    public string Read() {
        return Console.ReadLine();
    }
}

public class Copy
{
    private IReader reader;
    private IWriter writer;

    public Copy(IReader reader, IWriter writer) {
        this.reader = reader;
        this.writer = writer;
    }

    public void DoWork() {
        this.writer.Write(this.reader.Read());
    }
}
public interface IWriter
{
    void Write(string output);
}

public class PrinterWriter : IWriter
{
    public void Write(string output)
    {
        Console.WriteLine(output);
    }
}

public class FileWriter : IWriter
{
    public void Write(string output)
    {
        File.Write(output);
    }
}

Dependency Inversion Principle

Reader Example

Below code example demonstrates DIP implementation

public class Program
{
    public static void Main()
    {
        // Send output to Printer
        IReader r = new KeyboardReader();
        IWriter pw = new PrinterWriter();
        Copy cp = new Copy(r, pw);
        cp.DoWork();

        // Send output to FileStream now
        IWriter fw = new FileWriter();
        Copy cpf = new Copy(r, fw);
        cpf.DoWork();

        Console.ReadLine();
    }
}

Dependency Inversion Principle

OrderService Example

We have a class named OrderService which is used for processing orders.

It has a method named AcceptOrder which receives an Order object, validates that it is valid according to the business rules (it may for instance check that the order’s shipping address is a valid address and that there are enough items in stock) and then, given that the order is valid, saves it to a database using another class named OrderDatabase.

Dependency Inversion Principle

OrderService Example

The code for OrderService looks like this:

public class OrderService
{
    public void AcceptOrder(Order order)
    {
        //Domain logic such as validation

        new OrderDatabase().SaveOrder(order);
    }
}

Notice line seven above where AcceptOrder creates a new instance of the OrderDatabase class and then saves the order using it’s SaveOrder method. This might not be the most realistic scenario. In the real world SaveOrder could be static, making line seven in the example look like this:

OrderDatabase.SaveOrder(order);

Dependency Inversion Principle

OrderService Example

To fix this we apply Inversion of Control by creating an abstract definition of the behavior in OrderDatabase that OrderService needs in the form of the interface IOrderSaver and making AcceptOrder use an instance of IOrderSaver instead of an instance of OrderDatabase.

Dependency Inversion Principle

OrderService Example

OrderService no longer knows about the OrderDatabase class. It just uses an instance of the IOrderSaver interface of which OrderDatabase is one of possibly many implementation.

In fact we could easily change it so that OrderService instead uses a class that saves orders to XML files instead, without changing a single line of code in OrderService.

Or to a class that still saves to a database but that has been extended with logging.

Dependency Inversion Principle

OrderService Example

Or to a composite that saves both to a database and to an XML file. Or to…

Dependency Inversion Principle

OrderService Example

That’s all good, but how does OrderService get this instance of IOrderSaver you ask?

There would be little point in using the IOrderSaver interface if OrderService still had to know how to instantiate a concrete implementation of it, so we need a way for OrderService to get an instance of IOrderSaver at runtime without knowing about any concrete implementations.

There are a couple of ways to do that. Let’s begin by looking at Dependency Injection.

Dependency Inversion Principle

OrderService Example

Using Dependency Injection we inject an instance of IOrderSaver into OrderService when we use it.

This could either be done using constructor injection, where we supply the instance to OrderService’s constructor, or property injection where we supply it to OrderService after it has been instantiated by setting a property on it.

In the diagram below I’ve tried to illustrate constructor injection.

Dependency Inversion Principle

OrderService Example

Notice how OrderService has a constructor which requires an instance of IOrderSaver as a parameter. In code this could be implemented like this:

public class OrderService
{
    private IOrderSaver orderSaver;

    public OrderService(IOrderSaver orderSaver)
    {
        this.orderSaver = orderSaver;
    }

    public void AcceptOrder(Order order)
    {
        //Domain logic such as validation

        orderSaver.SaveOrder(order);
    }
}

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Let’s first define few example classes:

public class Engine {}

public class Car
{
    private Engine _engine;

    public Car()
    {
        _engine = new Engine();
    }
}

The example above we can see that Car (higher level component) depends on Engine (lower level component). At some point we may need to make car more reusable, for example to be able to work with different engines. It’s obvious that example above isn’t good enough since our Car class creates concrete Engine inside its constructor. 

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Dependency Inversion Principle

DIP principle simply states that higher level modules should not depend on lower level modules but both should depend on abstractions. Higher level module should also own this abstraction effectively now making lower level module depend on that abstraction and thus depend on higher level module.

Example:

public interface IEngine {}

public class Engine: IEngine {}

public class Car
{
    private IEngine _engine;
}

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Dependency Inversion Principle

In this example we can see that our Car now depends on abstraction rather then concrete Engine type, also concrete Engine implements our abstraction which must be packaged with Car to satisfy DIP principle. Another way of saying this would be that abstraction should represent Car needs not Engine possibilities. In this example I omitted Engine creation part on purpose since DIP principle doesn’t state exactly how dependencies should be created. Actually we can create them in few different ways, by using Dependency injection (DI), Service location or Factory pattern.

And so we came to DI pattern which is the way to facilitate the run-time provisioning of the chosen low-level component implementation to the high-level component.

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Dependency Injection pattern

DI pattern deals with the place where dependencies are created. In DI pattern dependency creation is not responsibility of dependent class but rather of its creator.

In the simplest form dependencies are injected through class constructor or by setting class properties.

Example:

public interface IEngine {}

public class Engine: IEngine {}

public class Car
{
    private IEngine _engine;

    public Car(IEngine engine)
    {
        _engine = engine;
    }
}

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Dependency Injection pattern

In this example we can see that concrete Engine type is being injected into Car class through constructor. Car class is not any more responsible of Engine creation meaning that concrete Engine implementation can be injected at runtime. 

Only one more thing left and that is the way we create our dependent class. We can simply instantiate all dependencies and inject them to dependent class at the time of creation.

var engine = new Engine();
var car = new Car(engine);

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Inversion of control

IoC container is responsible of object creation providing all necessary dependencies on the way. To do this it must know about all abstractions and all concrete types to use for that abstractions. To work with IoC container we must configure it first.

In the following example I will use Microsoft Unity container syntax.

Configuration:

var container = new UnityContainer()
    .RegisterType<IEngine, Engine>();

Dependency Inversion Principle

Dependency Inversion Principle, Dependency Injection and Inversion Of Control

Inversion of control

Resolve method above will create our Car object and it will automatically supply correct Engine in its constructor.

Complete example:

public interface IEngine {}

public class Engine: IEngine {}

public class Car
{
    private IEngine _engine;

    public Car(IEngine engine)
    {
        _engine = engine;
    }
}

var container = new UnityContainer().RegisterType<IEngine, Engine>();

var car = container.Resolve<Car>();

Dependency Inversion Principle

Когда выделять интерфейс (.NET interface) у класса

  • Класс является реализацией некоторой стратегии и будет использовать полиморфным образом.
  • Реализация класса работает с внешним окружением (файлами, сокетами, конфигурацией и т.п.).
  • Класс находится на стыке модулей.

Когда не нужно выделять интерфейс класса

  • Класс является неизменяемым «объектом-значением» (Value Object) или объектом-данных (Data Object).
  • Класс обладает стабильным поведением (не работает с внешним окружением).

Dependency Inversion Principle

Questions and answers

Questions and answers

Questions and answers

Questions and answers

Become SOLID in C#

By Pavel Nasovich

Become SOLID in C#

  • 1,798