模型的哪个部分应该处理数据库插入?

时间:2011-05-16 16:34:46

标签: c# single-responsibility-principle

标题可能并没有很好地描述我的问题,如果有人可以将其编辑为更合适的东西,我会很高兴。反正:

我有一个组件应该返回给定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将始终返回有意义的对象。

我的“一般想知道......”

成功进行webservice /网站调用后,我希望将获取的价格插入到本地数据库中,作为未来的后备案例。现在我的问题是:上面代码的哪一部分应该对此负责?它应该是具体的网络购买者之一返回价格吗?或“聚合器”提取器(MainFetcher),因为它也知道什么是价格来源?我应该举一些活动吗?用DB调用注入另一个接口?将设计改为更好?

为什么它甚至会成为我的问题?好吧,我试着保持代码干净(不用担心,这只是我的业余时间的宠物项目 - 正是为了解决这样的问题)可能与SRP / SoC有关。现在我似乎遇到了从这种心态转换的问题 - 我的意思是,取得网页的东西怎么可能也在做数据库插入?哦,来吧! :)

2 个答案:

答案 0 :(得分:2)

如果你想要一个超级解耦的设计,我会实现如下所示的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 :(得分:1)

听起来好像你需要一个“缓存”。缓存通常作为您注入Fetcher实现的一种方面或依赖项实现。下面我假设IPriceCacheIDictionary接口,但你当然可以插入你需要的任何抽象。我还建议抽取价格获取者的数据来源......:

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