ejemplos - patrones de diseño para c# pdf
¿Qué patrón de diseño usar para filtrar la consulta? do# (6)
¿No puedes agregar adónde vas aquí?
var products = datacontext.Products;
if(!String.IsNullOrEmpty(type))
products = products.Where(p => p.Type == type);
if(!String.IsNullOrEmpty(store))
products = products.Where(p => p.Store == store);
foreach(var p in products)
// Do whatever
o algo así...
Tengo una tabla de base de datos con una lista de productos (ropa). Los productos pertenecen a categorías y son de diferentes tiendas.
Categorías de muestra: tops, bottoms, shoes
Tiendas de muestra: gap.com, macys.com, target.com
Mis clientes pueden solicitar filtrar productos de las siguientes maneras:
- todos los productos (sin filtro)
- por categoria
- por tienda
- por categoría y tienda
En este momento, tengo UN método en mi clase "Productos" que devuelve los productos según el tipo de filtro solicitado por el usuario. Utilizo una enumeración FilterBy para determinar qué productos deben devolverse.
Por ejemplo, si el usuario desea ver todos los productos en la categoría "tops", yo llamo a esta función:
Products.GetProducts(FilterBy.Category, "tops", "");
Tengo el último parámetro vacío porque es la cadena que contiene la "tienda" para filtrar, pero en este caso no hay tienda. Sin embargo, si el usuario desea filtrar por categoría Y almacenar, llamaría al método de esta manera:
Product.GetProducts(FilterBy.CategoryAndStore, "tops", "macys.com");
Mi pregunta es, ¿cuál es la mejor manera de hacer esto? Acabo de enterarme del patrón de diseño de la estrategia. ¿Podría usar eso para hacer esto de una manera mejor (más fácil de extender y más fácil de mantener)?
La razón por la que hago esta pregunta es porque me parece que este debe ser un problema bastante común que las personas resuelven repetidamente (filtrando productos de varias maneras)
Creo que haría una clase de categoría y una clase de tienda, en lugar de solo cadenas:
class Category
{
public Category(string s)
{
...
}
...
}
Y luego tal vez:
Product.GetProducts(
Category category, //if this is null then don''t filter on category
Store store //if this is null then don''t filter on store
)
{
...
}
Las clases Category
y Store
pueden estar relacionadas (ambas pueden ser subclases de una clase Filter
).
El patrón de estrategia no necesariamente se adapta bien con el enfoque de repositorio basado en interfaz común. Personalmente, probablemente iría de dos maneras aquí:
Un método de búsqueda que admite combinaciones de opciones:
IList<Product> GetProducts(string category, string store, ...);
(luego aplique selectivamente las combinaciones de filtros (es decir, null
significa "cualquiera"), ya sea al construir un comando, o pase a un SPROC que hace algo similar.
Con LINQ, ¿tal vez una expresión de predicado?
IList<Product> GetProducts(Expression<Func<Product,bool>> predicate);
Por supuesto, con LINQ también puede usar la composición de la persona que llama, pero es más difícil escribir un repositorio cerrado / completamente probado para:
`IQueryable<Product> Products {get;}`
(y hacer que quien llama use .Where (x => x.Category == "foo")) - No estoy tan seguro de este último a largo plazo ...
Estoy respondiendo esto basado en mi pequeño conocimiento de patrones.
El patrón de decorador puede ayudar aquí (considerando que puede agregar un filtro y obtener resultados. Aplicar nuevos filtros y obtener nuevos resultados)
Me gustaría ir con algo así como una estrategia para los filtros en sí mismos y escribir CategoryFilter y StoreFilter classes. Entonces usaría un compuesto o un decorador para combinar los filtros.
Según el "Diseño de unidad de dominio" de Eric Evan, necesita el patrón de especificación. Algo como esto
public interface ISpecification<T>
{
bool Matches(T instance);
string GetSql();
}
public class ProductCategoryNameSpecification : ISpecification<Product>
{
readonly string CategoryName;
public ProductCategoryNameSpecification(string categoryName)
{
CategoryName = categoryName;
}
public bool Matches(Product instance)
{
return instance.Category.Name == CategoryName;
}
public string GetSql()
{
return "CategoryName like ''" + { escaped CategoryName } + "''";
}
}
Su repositorio ahora se puede llamar con especificaciones
var specifications = new List<ISpecification<Product>>();
specifications.Add(
new ProductCategoryNameSpecification("Tops"));
specifications.Add(
new ProductColorSpecification("Blue"));
var products = ProductRepository.GetBySpecifications(specifications);
También podría crear una clase genérica de CompositeSpecification que contuviera especificaciones secundarias y un indicador de qué operador lógico aplicarles Y / O
Sin embargo, me inclinaría más a combinar expresiones LINQ.
Actualización - Ejemplo de LINQ en tiempo de ejecución
var product = Expression.Parameter(typeof(Product), "product");
var categoryNameExpression = Expression.Equal(
Expression.Property(product, "CategoryName"),
Expression.Constant("Tops"));
Puede agregar un "y" como tal
var colorExpression = Expression.Equal(
Expression.Property(product, "Color"),
Expression.Constant("Red"));
var andExpression = Expression.And(categoryNameExpression, colorExpression);
Finalmente puede convertir esta expresión en un predicado y luego ejecutarlo ...
var predicate =
(Func<Product, bool>)Expression.Lambda(andExpression, product).Compile();
var query = Enumerable.Where(YourDataContext.Products, predicate);
foreach(Product currentProduct in query)
meh(currentProduct);
Probablemente no se compile porque lo he escrito directamente en el navegador, pero creo que generalmente es correcto.
Otra actualización :-)
List<Product> products = new List<Product>();
products.Add(new Product { CategoryName = "Tops", Color = "Red" });
products.Add(new Product { CategoryName = "Tops", Color = "Gree" });
products.Add(new Product { CategoryName = "Trousers", Color = "Red" });
var query = (IEnumerable<Product>)products;
query = query.Where(p => p.CategoryName == "Tops");
query = query.Where(p => p.Color == "Red");
foreach (Product p in query)
Console.WriteLine(p.CategoryName + " / " + p.Color);
Console.ReadLine();
En este caso, estaría evaluando en memoria porque la fuente es una lista, pero si su fuente era un contexto de datos que admitía Linq2SQL, por ejemplo, creo que esto se evaluaría usando SQL.
Aún puede usar el patrón de Especificación para hacer que sus conceptos sean explícitos.
public class Specification<T>
{
IEnumerable<T> AppendToQuery(IEnumerable<T> query);
}
La diferencia principal entre los dos enfoques es que este último crea una consulta conocida basada en propiedades explícitas, mientras que el primero se puede usar para construir una consulta de cualquier estructura (como crear una consulta enteramente desde XML, por ejemplo).
Esto debería ser suficiente para comenzar :-)