Какая часть модели должна обрабатывать вставки базы данных? - PullRequest
2 голосов
/ 16 мая 2011

Заголовок, вероятно, не слишком хорошо описывает мою проблему, я буду рад, если кто-нибудь сможет отредактировать ее для чего-то более подходящего.В любом случае:

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

interface IProductPriceFetcher
{
    double GetPrice(int id);
}

Теперь цену можно получить из 3 различных источников:

  • веб-сервис
  • непосредственно из исходного кода веб-сайта (утилизация)
  • в качестве окончательного отката (и веб-сервис, и веб-сайт недоступны) возвращается самая последняя цена из локальной базы данных

Чтобы поиграть с этимПроблема с 3 различными источниками Я реализовал класс следующим образом:

class MainFetcher : IProductPriceFetcher
{
    public double GetPrice(int id)
    {
        var priceFetcher = this.factory.GetWebServiceFetcher()
                        ?? this.factory.GetWebsiteFetcher()
                        ?? this.factory.GetLocalDatabaseFetcher();
        return priceFetcher.GetPrice(id);
    }
}

Каждый метод фабрики возвращает, конечно же, IProductPriceFetcher, с дополнительным замечанием, что первые два могут завершиться с ошибкой и вернуть null;Я предполагал, что GetLocalDatabaseFetcher всегда будет возвращать значимый объект, хотя.

Мое "общее удивление ..."

После успешного вызова веб-сервиса / веб-сайта я хочу, чтобы выбранная цена была вставлена ​​в локальную базу данных., как будущий запасной вариант.Теперь мой вопрос: какая часть кода выше должна отвечать за это?Должен ли он быть одним из конкретных веб-сборщиков, который возвращает цену?Или сборщик «агрегатор» (MainFetcher), поскольку он также знает, что является источником цены?Должен ли я поднять какое-то событие?Вставить еще один интерфейс с вызовами БД?Изменить дизайн на более качественный?

Почему это вообще стало для меня проблемой?Ну, я попытался сохранить код чистым (не беспокойтесь, это всего лишь любимый проект для моего свободного времени - именно для решения подобных проблем), возможно, с учетом SRP / SoC.Теперь у меня, кажется, есть проблемы с переключением с этого мышления - я имею в виду, как может что-то, что выбирает веб-страницы, также делать вставки базы данных?О, давай!:)

Ответы [ 2 ]

2 голосов
/ 16 мая 2011

Если вы хотите создать супер-развязанный дизайн, я бы реализовал класс Decorator , подобный следующему, и использовал бы его, чтобы обернуть и WebServiceFetcher, и WebsiteFetcher:

class DatabaseCachingFetcherDecorator : IProductPriceFetcher
{
    private readonly IProductPriceFetcher innerFetcher;

    public DatabaseCachingFetcherDecorator(IProductPriceFetcher fetcher)
    {
        this.innerFetcher = fetcher;
    }

    public double GetPrice(int id)
    {
        double price = this.innerFetcher.GetPrice(id);

        if (price != 0) // or some other value representing "price not found"
        {
            SavePriceToDatabase(id, price);
        }

        return price;
    }

    private SavePriceToDatabase(int id, double price)
    {
        // TODO: Implement...
    }
}

Тогда ваша фабрика будет реализовывать следующие методы:

public IProductPriceFetcher GetWebServiceFetcher()
{
    return new DatabaseCachingFetcherDecorator(new WebServiceFetcher());
}

public IProductPriceFetcher GetWebsiteFetcher()
{
    return new DatabaseCachingFetcherDecorator(new WebsiteFetcher());
}

Этот дизайн отделяет ваши фактические средства извлечения от вашего механизма кэширования.

РЕДАКТИРОВАТЬ: Я немного неправильно понял ваш дизайн с этимответ, так как я предполагал, что метод GetPrice будет возвращать какое-то значение NULL, если цена не может быть получена, вместо того, чтобы фабрика возвращала значение NULL.Я думаю, что фабрика, возвращающая NULL, немного пахнет, поскольку фабрика несет ответственность за надежное возвращение объектов.Я хотел бы рассмотреть возможность изменения интерфейса вашего метода GetPrice, чтобы он возвращал double?, возможно, для учета "цена не найдена".

1 голос
/ 16 мая 2011

Звучит так, будто вам нужен "кэш". Кэширование обычно реализуется как вид аспекта или зависимости, который вы внедряете в реализацию Fetcher. Ниже я предполагаю IPriceCache с интерфейсом IDictionary, но вы, конечно, можете вставить любую абстракцию, которая вам нужна. Я также предлагаю абстрагироваться от источников данных для сборщиков цен ...:

class MainFetcher : IPriceFetcher {

 IEnumerable< IPriceSource > mSource;
 IPriceCache mCache;

 public MainFetcher( IEnumerable< IPriceSource > pSource, IPriceCache pCache )
 {
     mSource = pSource;
     mCache = pCache; 
 }

 public double GetPrice(int pID)
 {
     double tPrice;
     // get from cache
     if (mCache.TryGet(pID, out tPrice) {
         return tPrice;
     } else {
         // throws if no source found
         tPrice = mSource
             .First(tArg => tArg != null)
             .GetPrice(pID);
         // add to cache
         mCache.Add(pID, tPrice);
     }
 }
}
...