Esta es la solución de Nelo: http://nelopauselli.blogspot.com.ar/2012/07/implementando-solidocp.html
y la de Martín: http://blog.martincoll.com.ar/refactorizando-para-cumplir-con-ocp/
A veces, cuando miramos / escribimos código, lo hacemos mirando dentro del método. Pero que pasa si esta vez nos paramos fuera del método, del lado de quien usa el código? El amigo Carlos Peix escribió un muy buen post al respecto.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
class FilterTest | |
{ | |
[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)); | |
} | |
[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)); | |
} | |
[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)); | |
} | |
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) | |
}; | |
} | |
} |
- filtrar por color
- filtrar por talle
- filtrar por color y talle
Para agregar criterios sin tener que modificar la clase existente, una posible solución es tener una clase por criterio (especificación). En este caso estamos dando a la clase una única razón para cambiar y aplicando también el principio de responsabilidad única.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
class FilterTest | |
{ | |
[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)); | |
} | |
[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)); | |
} | |
[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)); | |
} | |
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) | |
}; | |
} | |
} |
Se dice que la decisión qué parte es fija (dura de cambiar) y cual es móvil (fácil de cambiar / extender) en una aplicación es una decisión estratégica. No puedo hacer todo open / closed, ya que con eso pago un costo de complejidad. La parte que me conviene encapsular p/hacer movil es la que varía.