lunes, 16 de julio de 2012

Implementando SOLID - OCP

Ahora vamos con el segundo principio, conocido como open/closed o abierto/cerrado. Que dice que una clase tiene que ser abierta para extensión y cerrada para modificación.

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.

[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)
};
}
}
view raw FilterTest.cs hosted with ❤ by GitHub
En este caso vemos que se trata de un filtro que permite:
  • filtrar por color
  • filtrar por talle
  • filtrar por color y talle
Si tuviese que agregar otro criterio p/filtrar, entonces tengo que agregar un nuevo método, lo que implica modificar la clase y por lo tanto no cumple con ser cerrada para modificación.

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.

[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)
};
}
}
view raw FilterTest.cs hosted with ❤ by GitHub
Ahora, para agregar un criterio (extender), no es necesario modificar las clases existentes.

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.

No hay comentarios.: