jueves, 12 de julio de 2012

Implementando SOLID - SRP

Como parte del taller de principios solid que hicimos con los amigos de Kleer, junto con Nelo y Juan nos propusimos explicar paso por paso como implementamos los principios tomando los ejemplos de http://solidexamples.codeplex.com/

Así que vamos a empezar por el principio de responsabilidad única (single responsability / SRP), que nos dice que una clase debe tener una única razón para cambiar.

Esta es la resolución de Nelo: http://nelopauselli.blogspot.com.ar/2012/07/implementado-solid-srp.html
la de Martín Collhttp://blog.martincoll.com.ar/refactorizando-para-cumplir-con-srp/
la de Juan Tripodehttp://altaprogramacion.blogspot.com.ar/2012/08/srp-single-responsibility-principle.html

El ejemplo trata de una aplicación winform que permite abrir un xml de productos y mostrarlos en una grilla. En este se programa todo "atrás del botón"
private void btnLoad_Click(object sender, EventArgs e)
{
listView1.Items.Clear();
var fileName = txtFileName.Text;
using (var fs = new FileStream(fileName, FileMode.Open))
{
var reader = XmlReader.Create(fs);
while (reader.Read())
{
if (reader.Name != "product") continue;
var id = reader.GetAttribute("id");
var name = reader.GetAttribute("name");
var unitPrice = reader.GetAttribute("unitPrice");
var discontinued = reader.GetAttribute("discontinued");
var item = new ListViewItem(new string[] { id, name, unitPrice, discontinued });
listView1.Items.Add(item);
}
}
}
view raw form.cs hosted with ❤ by GitHub
El formulario es responsable de la persistencia, de recuperar la información (de un archivo) y de conocer el formato (xml). El primer paso que proponemos es encapsular esto en un repositorio, por lo que nos quedaría algo así:
private void btnLoad_Click(object sender, EventArgs e)
{
listView1.Items.Clear();
var fileName = txtFileName.Text;
foreach (Product product in productRepository.GetByFileName(fileName))
{
var item = new ListViewItem(new[] {
product.Id.ToString(),
product.Name,
product.UnitPrice.ToString(),
product.Discontinued.ToString()
});
listView1.Items.Add(item);
}
}
view raw form.cs hosted with ❤ by GitHub
class ProductRepository : IProductRepository
{
public IEnumerable<Product> GetByFileName(string fileName)
{
var products = new List<Product>();
using (var fs = new FileStream(fileName, FileMode.Open))
{
var reader = XmlReader.Create(fs);
while (reader.Read())
{
if (reader.Name != "product") continue;
var product = new Product();
product.Id = int.Parse(reader.GetAttribute("id"));
product.Name = reader.GetAttribute("name");
product.UnitPrice = decimal.Parse(reader.GetAttribute("unitPrice"));
product.Discontinued = bool.Parse(reader.GetAttribute("discontinued"));
products.Add(product);
}
}
return products;
}
}
Ahora el repositorio es responsable de recuperar datos, abrir el archivo y convertir de xml a objetos. Vamos a quitarle la responsabilidad de abrir el archivo:
class FileLoader : IFileLoader
{
public Stream Load(string fileName)
{
return new FileStream(fileName, FileMode.Open);
}
}
view raw FileLoader.cs hosted with ❤ by GitHub
class ProductRepository : IProductRepository
{
private readonly IFileLoader loader;
public ProductRepository()
{
loader = new FileLoader();
}
public IEnumerable<Product> GetByFileName(string fileName)
{
var products = new List<Product>();
using (Stream input = loader.Load(fileName))
{
var reader = XmlReader.Create(input);
while (reader.Read())
{
if (reader.Name != "product") continue;
var product = new Product();
product.Id = int.Parse(reader.GetAttribute("id"));
product.Name = reader.GetAttribute("name");
product.UnitPrice = decimal.Parse(reader.GetAttribute("unitPrice"));
product.Discontinued = bool.Parse(reader.GetAttribute("discontinued"));
products.Add(product);
}
}
return products;
}
}
Y la responsabilidad de convertir xml a objetos:
class ProductRepository : IProductRepository
{
private readonly IFileLoader loader;
private readonly IProductsMapper mapper;
public ProductRepository()
{
loader = new FileLoader();
mapper = new ProductsMapper();
}
public IEnumerable<Product> GetByFileName(string fileName)
{
IEnumerable<Product> products;
using (Stream file = loader.Load(fileName)) {
products = mapper.Map(file);
}
return products;
}
}
class ProductsMapper : IProductsMapper
{
private Product MapItem(XmlReader reader)
{
if (reader.Name != "product") throw new InvalidOperationException("XML reader is not on a product fragment.");
var product = new Product();
product.Id = int.Parse(reader.GetAttribute("id"));
product.Name = reader.GetAttribute("name");
product.UnitPrice = decimal.Parse(reader.GetAttribute("unitPrice"), CultureInfo.InvariantCulture);
product.Discontinued = bool.Parse(reader.GetAttribute("discontinued"));
return product;
}
public IEnumerable<Product> Map(Stream stream)
{
if (stream == null) throw new ArgumentNullException("Stream used when mapping cannot be null.");
var reader = XmlReader.Create(stream);
var products = new List<Product>();
while (reader.Read())
{
if (reader.Name != "product") continue;
var product = MapItem(reader);
products.Add(product);
}
return products;
}
}
Ahora el repositorio tiene otra responsabilidad, la de conocer sus dependencias. Pero esto es otro principio. saludos!

No hay comentarios.: