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

C# 模型的哪个部分应该处理数据库插入?,c#,single-responsibility-principle,C#,Single Responsibility Principle,标题可能不能很好地描述我的问题,如果有人能把它编辑成更合适的内容,我会很高兴的。无论如何: 我得到了一个组件,根据它的id应该返回产品价格。它实现了这样一个接口: interface IProductPriceFetcher { double GetPrice(int id); } 现在,可以从3个不同的来源获取价格: 网络服务 直接来自网站源代码(报废) 作为最后的回退(webservice和网站都不可访问),将返回本地数据库中的最新价格 为了解决这个3种不同来源的问题,我实现了

标题可能不能很好地描述我的问题,如果有人能把它编辑成更合适的内容,我会很高兴的。无论如何:

我得到了一个组件,根据它的
id
应该返回产品价格。它实现了这样一个接口:

interface IProductPriceFetcher
{
    double GetPrice(int id);
}
现在,可以从3个不同的来源获取价格:

  • 网络服务
  • 直接来自网站源代码(报废)
  • 作为最后的回退(webservice和网站都不可访问),将返回本地数据库中的最新价格
为了解决这个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
将始终返回有意义的对象tho

我的“将军”疑惑 成功的webservice/website调用后,我希望将获取的价格插入本地数据库,作为未来的回退案例。现在我的问题是:上面代码的哪一部分应该对此负责?它应该是一个具体的网站抓取返回价格?或者“聚合器”取数器(
MainFetcher
),因为它也知道价格的来源?我应该提出一些事件吗?用DB调用注入另一个接口?改变设计使之更好


为什么这对我来说是个问题?嗯,我试图保持代码干净(不用担心,这是我业余时间唯一的宠物项目——正是为了解决这样的问题),可能是考虑到了SRP/SoC。现在,我似乎很难改变这种心态——我的意思是,获取网页的东西怎么可能也在做数据库插入呢?哦,来吧!:)

如果您想要一个超级解耦的设计,我将实现如下类,并使用它包装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的工厂有点异味,因为工厂的责任是可靠地返回对象。我会考虑更改您的<代码> GETPASS/<代码>方法接口,以返回<代码>双-<代码>,以允许“价格未被找到”。

< P> >在我看来,如果您需要一个“缓存”。缓存通常是作为一种方面或依赖项实现的,您可以将其注入
获取程序
实现中。下面我假设
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);
     }
 }
}
类主取数器:IPriceFetcher{
IEnumerablemSource;
IPriceCache-mCache;
公共主取数器(IEnumerablepSource,IPriceCache pCache)
{
mSource=pSource;
mCache=pCache;
}
公共双GetPrice(int-pID)
{
双tPrice;
//从缓存中获取
if(mCache.TryGet(pID,out tPrice){
返回tPrice;
}否则{
//如果找不到源,则抛出
tPrice=mSource
.第一个(tArg=>tArg!=null)
.GetPrice(pID);
//添加到缓存
添加(pID、tPrice);
}
}
}