какой шаблон дизайна использовать для фильтрации запросов? C # - PullRequest
10 голосов
/ 08 февраля 2009

У меня есть таблица базы данных со списком товаров (одежда). Товары относятся к категориям и из разных магазинов.

Примеры категорий: топы, низы, обувь

Примеры магазинов: gap.com, macys.com, target.com

Мои клиенты могут запросить фильтрацию продуктов следующими способами:

  • все продукты (без фильтра)
  • по категории
  • в магазине
  • по категориям и магазину

Прямо сейчас у меня есть ОДИН метод в моем классе "Продукты", который возвращает продукты в зависимости от типа фильтра, запрошенного пользователем. Я использую перечисление FilterBy, чтобы определить, какие продукты необходимо вернуть.

Например, если пользователь хочет просмотреть все товары в категории "топы", я вызываю эту функцию:

Products.GetProducts(FilterBy.Category, "tops", ""); 

У меня последний параметр пуст, потому что это строка, которая содержит «хранилище» для фильтрации, но в этом случае нет хранилища. Однако, если пользователь хочет фильтровать по категории И хранить, я бы вызвал метод следующим образом:

Product.GetProducts(FilterBy.CategoryAndStore, "tops", "macys.com");

Мой вопрос: как лучше это сделать? Я только что узнал о шаблоне разработки стратегии. Могу ли я использовать это, чтобы сделать это лучше (легче расширять и легче поддерживать)?

Причина, по которой я задаю этот вопрос, заключается в том, что я полагаю, что это должно быть довольно распространенной проблемой, которую люди неоднократно решают (фильтруя продукты различными способами)

Ответы [ 6 ]

13 голосов
/ 08 февраля 2009

Согласно «Эскизу Эвану» «Domain Drive Design», вам нужен шаблон спецификации. Как то так

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 } + "'";
  }
}

Ваш репозиторий теперь можно вызывать со спецификациями

var specifications = new List<ISpecification<Product>>();
specifications.Add(
 new ProductCategoryNameSpecification("Tops"));
specifications.Add(
 new ProductColorSpecification("Blue"));

var products = ProductRepository.GetBySpecifications(specifications);

Вы также можете создать универсальный класс CompositeSpecification, который будет содержать под спецификации и указывать, какой логический оператор применять к ним И / ИЛИ

Хотя я бы более склонен комбинировать выражения LINQ.

Обновление - пример LINQ во время выполнения

var product = Expression.Parameter(typeof(Product), "product");
var categoryNameExpression = Expression.Equal(
  Expression.Property(product, "CategoryName"),
  Expression.Constant("Tops"));

Вы можете добавить "и", как это так

var colorExpression = Expression.Equal(
  Expression.Property(product, "Color"),
  Expression.Constant("Red"));
var andExpression = Expression.And(categoryNameExpression, colorExpression);

Наконец, вы можете преобразовать это выражение в предикат и затем выполнить его ...

var predicate = 
  (Func<Product, bool>)Expression.Lambda(andExpression, product).Compile();
var query = Enumerable.Where(YourDataContext.Products, predicate);

foreach(Product currentProduct in query)
  meh(currentProduct);

Вероятно, не будет компилироваться, потому что я набрал его прямо в браузере, но я считаю, что в целом это правильно.

Еще одно обновление : -)

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();

В этом случае вы будете выполнять оценку в памяти, поскольку источником является список, но если вашим источником является контекст данных, который поддерживает Linq2SQL, например, я думаю, , это будет оцениваться с использованием SQL.

Вы все еще можете использовать шаблон Спецификации, чтобы сделать ваши концепции явными.

public class Specification<T>
{
  IEnumerable<T> AppendToQuery(IEnumerable<T> query);
}

Основное различие между этими двумя подходами заключается в том, что последний создает известный запрос на основе явных свойств, тогда как первый может использоваться для создания запроса любой структуры (например, для создания запроса исключительно из XML).

Этого должно быть достаточно, чтобы начать: -)

2 голосов
/ 08 февраля 2009

Шаблон стратегии не обязательно хорошо сочетается с обычным интерфейсным подходом к хранилищу. Лично я бы, наверное, пошел одним из двух способов:

  • Один метод поиска, который поддерживает комбинации параметров:

    IList<Product> GetProducts(string category, string store, ...);

(затем выборочно применять комбинации фильтров (т. Е. null означает «любой») - либо при построении команды, либо передать SPROC, который делает нечто подобное.

  • С LINQ, может быть, предикатное выражение?

    IList<Product> GetProducts(Expression<Func<Product,bool>> predicate);

Конечно, с LINQ вы также можете использовать композицию вызывающей стороной, но это труднее написать закрытый / полностью протестированный репозиторий для:

 `IQueryable<Product> Products {get;}`

(и использовать вызывающего абонента. Где (x => x.Category == "foo")) - я не уверен в этом последнем долгосрочном ...

1 голос
/ 08 февраля 2009

Я думаю, я бы создал класс Category и класс Store, а не просто строки:

class Category
{
  public Category(string s)
  {
    ...
  }
  ...
}

А потом может быть:

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
  )
{
  ...
}

Классы Category и Store могут быть связаны (оба они могут быть подклассами класса Filter).

0 голосов
/ 08 февраля 2009

Не могли бы вы просто добавить Куда вещи, когда вы идете сюда?

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

или что-то в этом роде ...

0 голосов
/ 08 февраля 2009

Я бы выбрал стратегию для самих фильтров и написал бы CategoryFilter и StoreFilter классов. Тогда я бы использовал композит или декоратор для объединения фильтров.

0 голосов
/ 08 февраля 2009

Я отвечаю на это, основываясь на своих небольших знаниях шаблонов.

Здесь может помочь шаблон Decorator (учитывая, что вы можете добавить фильтр и получить результаты. Применить новые фильтры к нему и получить новые результаты)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...