skype: pavel.nasovich
email: forcewake@gmail.com
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"));
}
}
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.
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 |
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.
Название: Принцип единственной ответственности.
Определение: У класса/модуля должна быть лишь одна причина для изменения.
Смысл принципа: Борьба со сложностью, важность которой резко возрастает при развитии логики приложения.
Краткое описание: Любой сложный класс должен быть разбит на несколько простых составляющих, отвечающих за определенный аспект поведения, что упрощает как понимание, так и будущее развитие.
Two points of change for your application.
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.
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.
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.
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.
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.
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);
}
}
Название: Принцип открытости/закрытости
Определение: Программные сущности (классы, модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации.
Смысл принципа: ограничить распространение изменений минимальным числом классов/модулей; позволить вести параллельную разработку путем фиксации интерфейсов классов и открытости реализаций.
Краткое описание: закрытость модулей означает стабильность интерфейса и возможность использования классов/модулей клиентами. Открытость модулей - это возможность внесения изменений в поведении, путем изменения реализации или же путем переопределения.
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;
}
}
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:
public class Circle
{
public double Radius { get; set; }
}
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;
}
}
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?).
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.
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;
}
}
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.
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.
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
}
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;
}
}
}
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));
}
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));
}
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));
}
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.
class ProductFilter
{
public IEnumerable<Product> By(
IList<Product> products,
IFilterSpecification filterSpecification)
{
return filterSpecification.Filter(products);
}
}
interface IFilterSpecification
{
IEnumerable<Product> Filter(IList<Product> products);
}
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;
}
}
}
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;
}
}
}
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;
}
}
}
}
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));
}
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));
}
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));
}
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;
}
}
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;
}
}
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();
}
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;
}
}
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();
}
}
Lets create an interface which is used for each handler (for '$' and '!')
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.
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:
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);
}
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`
}
}
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.
Название: Принцип замещения Барбары Лисков
Определение: Должна быть возможность вместо базового типа подставить любой его подтип.
Смысл: Реализуйте наследование подтипов правильно.
Краткое описание: для корректной реализации отношения «ЯВЛЯЕТСЯ», наследник может ослаблять предусловие и усиливать постусловие (требовать меньше и гарантировать больше), при этом инварианты базового класса должны выполняться наследником. При нарушении этих правил подстановка экземпляров наследника в метод, принимающий базовый класс будет приводить к непредсказуемым последствиям.
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.
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.
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;
}
}
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?
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.
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();
}
}
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;
}
}
A Simple Example
Both solutions have their own drawbacks.
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).
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 */
}
}
}
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.
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).
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
}
}
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
Whenever you're feeling the urge to check the subtype of an object, you're most likely doing it WRONG.
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...
}
}
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"
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");
}
}
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:
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);
}
}
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 ;)");
}
}
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();
}
}
}
Ostrich Example
New specification.
And we add...
Ostrich:
class Ostrich : Bird
{
public override void Fly()
{
//Oops....
Console.WriteLine("I wish i could but ....");
}
}
Using Contracts to discover Liskov Substitution Principle Violations
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:
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; } }
}
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);
}
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.
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.
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 { ... }
}
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();
}
}
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();
}
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();
}
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 }
}
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.
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.).
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!";
}
}
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.
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.
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.
Название: Принцип разделения интерфейсов
Определение: клиенты не должны вынужденно зависеть от методов, которыми не пользуются.
Смысл: класс должен предоставлять удобный интерфейс с точки зрения его разнообразных клиентов.
Краткое описание: интерфейс класса должен быть цельным и согласованным не зависимо от числа клиентов. Несколько разных клиентов вполне могут использовать лишь подмножество методов класса, до тех пор, пока интерфейс класса будет оставаться согласованным. Проблемы появляются тогда, когда интерфейс класса начинает распухать или появляются разные методы с похожей семантикой лишь для того, чтобы ими было удобно пользоваться определенным клиентам.
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.
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.
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; }
}
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.
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; }
}
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; }
}
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!
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.
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.
MembershipProvider Example
Solution
The provider could have been divided in several parts:
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.
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.
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
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
}
}
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);
}
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.
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);
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();
}
}
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.
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.
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
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.
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);
}
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);
}
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.
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);
}
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);
}
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();
}
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();
}
}
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
}
}
Существует несколько причин для выделения у класса дополнительных интерфейсов.
Во-первых, может появиться необходимость выделить интерфейс для класса целиком. Это бывает необходимым для «изменчивых» зависимостей (классов, взаимодействующих с внешним миром) или для стратегий (когда нам нужно полиморфное поведение, определенное интерфейсом).
Во-вторых, мы можем выделить некий аспект текущего класса, который покрывает не весь функционал, а лишь его часть, т.е. его отдельный аспект. Примером может служить добавление классу «ролевых» интерфейсов (Role Interface), типа ICloneable, IComparable<T>, IEquatable<T> и т.п. При этом выделение таких интерфейсов обычно требуется для использования класса в новом контексте (класс Person реализуется IComparable<T> для корректного использования в SortedList).
В-третьих, может потребоваться выделение специализированных бизнес-интерфейсов, когда станет очевидным, что наш класс используется в нескольких разных контекстах. Например, когда репозиторий используется разными клиентами двумя способами: одна группа клиентов использует лишь операции для чтения данных, а другая группа – для обновления. Обычно это говорит не столько о нарушении ISP, сколько о наличии скрытой абстракции (IRepositoryReader и IRepositoryWriter).
В-четвертых, нам может потребоваться использовать несколько разных классов из разных иерархий наследования полиморфным образом. При этом обычно такое выделение происходит во время рефакторинга, когда необходимость в этом становится очевидной:
«Ага, вот у нас есть два класса, которые мы могли бы использовать совместно, но они уже унаследованы от разных базовых классов. Давайте выделим для каждого из них единый интерфейс и избавимся от богомерзких if-else».
TL;DR
Название: Принцип инверсии зависимостей
Определение: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций.
Смысл: сделать ключевые и/или изменчивые зависимости класса явными.
Краткое описание: слишком большое число зависимостей класса говорит о проблемах в дизайне. Возможно класс делает слишком многое, или же текущий класс не удачен, что приводит к необходимости дергания по одному методу у слишком большого числа зависимостей.
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();
}
}
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.)
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
}
}
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.
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.
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.
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();
}
}
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.
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);
}
}
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();
}
}
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.
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);
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.
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.
OrderService Example
Or to a composite that saves both to a database and to an XML file. Or to…
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.
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.
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 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 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 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 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 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 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 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>();
Когда выделять интерфейс (.NET interface) у класса
Когда не нужно выделять интерфейс класса