应用坚实的原则,不要重复太多代码



我试着用一些简单的类来实践坚实的原理。基本上,我想从产品a和产品B中获取一些字段,以适应一个通用类product。我希望能够根据货币来计算价格。我单独设计了GetInUSDollars(),因为我想在不改变现有类的情况下保留更多货币的可能性。但是,尝试为一个函数使用一个类似乎会产生很多重复。我应该朝着什么方向去减少重复的数量?

公共类ProductAData:IProduct{公共列表<产品>GetAll(){return(来自新ProductARepository().GetAll()中的n选择新产品{Id=n.Id,model=n.model,Price=n.Price,Type="ProductA"})。ToList();}}公共类ProductADataInUSD美元:IProductInUSD美元{公共列表<产品>GetInUSDollars(){return(来自新PhoneCaseRepository()中的n。GetAll()选择新产品{Id=n.Id,model=n.model,Price=n.Price*0.85,Type="ProductA"})。ToList();}}

还有另一个类也应该适合Product类。

公共类ProductBCaseData:IProduct{公共列表<产品>GetAll(){return(来自新ProductBRository().GetAll()中的n选择新产品{Id=n.Id,model=n.model,Price=n.Price,Type="ProductB"})。ToList();}}公共类ProductBDataInUSD美元:IProductInUSD美元{公共列表<产品>GetInUSDollars(){return(来自新ProductBRository().GetAll()中的n选择新产品{Id=n.Id,model=n.model,Price=n.Price*0.85,Type="ProductB"})。ToList();}}

您通常不希望为每种货币都有一个方法,它孕育了条件逻辑,您必须在其中进行一些计算来确定要调用的方法(破坏依赖反转主体)。

相反,您反转控件,并在通用接口后面注入所需的功能,例如:

public interface IProductInCurrency 
{
IList<Product> GetInCurrency();
}
public class ProductBDataInUSDollars: IProductInCurrency
{
public IList<Product> GetInCurrency()
{
return (from n in new ProductBRepository().GetAll()
select new Product { Id = n.Id, model= n.Model, 
Price = n.Price * 0.85, Type = "ProductB" }).ToList();
}
}

您已经通过接口屏蔽了控件,并在具体实现中屏蔽了实现的细节。您的代码总是接受接口(构造函数或方法参数),但您传递了所需的具体内容。即,您的实现代码针对接口工作,在您的示例中,它就像一个转换器,并且您传入所需的转换器(在本示例中为美元实现,您将为您支持的每种货币重新实现IProductInCurrency)。

就固体而言,这种方式:

  1. 保留单一责任。IProductInCurrency的每个实现只做一件事——以正确的货币提供产品列表
  2. Open Closed Principal-它可以通过为IProductInCurreny实现具体来扩展(添加更多货币),但仍然关闭以进行修改(他们无法访问使用该接口的代码)
  3. Liskov-您可以用IProductInCurrency的另一个实现来替换一个具体实现,而无需更改应用程序的逻辑
  4. 接口分离-我们有用于单个操作的接口,而不是一个巨大的接口,所以这里仍然很好
  5. 依赖性反转——我开始的示例解释的核心——没有任何特定的方法会破坏条件逻辑(例如GetInUSDollars()会破坏这个主体)。相反,我们颠倒控制并在需要的地方注入依赖项

从原理和解决方案来看,我认为以下想法在一定程度上适用:

单一责任原则

你不想要一个处理所有货币的神对象,所以你继续按货币划分类。然而,您最终复制了代码,引入了具有相同责任的多个对象,这可能与原则相矛盾。

打开-关闭原理

很难说你是否扩展了你的对象,而不是用代码库中的开关修改它们的行为,因为我们只看到了接口实现,而没有看到太多其他实现。我想说,你在这里走上了正确的道路,因为我们没有看到一个类别具有多种货币汇率的switch

利斯科夫代换原理

很难说基于代码基示例——这里似乎不依赖于任何更高级别的抽象(BaseCurrencyBaseProduct等)。相反,您定义了相当具体的接口,这些接口意味着具体的实现。

界面分离原则

我想你有这个,所以我跳过。

依赖反转原理

这就是我的答案。我相信你目前不依赖任何东西,只依赖你的具体汇率。费率肯定不是固定不变的,而且一天会变化多次——所以硬编码这些听起来不太可取。然而,如果您选择在代码中注入Currency的抽象,它应该允许您将这些分支实际折叠成逻辑代码块,每个代码块都做各自的事情:获取数据(您已经得到了)将DB实体映射到DTO(我认为您正是这样做的),最后应用业务逻辑,如货币转换。我想说,你可以更进一步,引入一个BaseProduct类,而不是将Type硬编码到每个类中——选择派生类,如class ProductA: Product {}class ProductB:Product {}——然后你将依赖于其他任何地方的抽象,并且可以在每次不咨询Type属性的情况下对每个抽象采取不同的操作(不过,我将把这个练习留给你来完成)

有了以上内容,我建议采用如下结构:

#region Currencies definitions
class Currency { 
public double ExchangeRate {get;set;} // suppose this defines relation of a given currency to "base" currency your application uses internally
public string Tag {get;set;} // just some means to identify it. you can use numeric Ids or anything else you deisre
public Currency(double exchangeRate, string tag) {
ExchangeRate = exchangeRate; 
Tag = tag;
}
}
class AUD : Currency // you don't have to define classes with hardcoded values, but I'd do it for example
{
public AUD() : base(1.5, "AUD") {}
}
class NZD : Currency
{
public NZD() : base(1.6, "NZD") { }
}
#endregion 
#region Product classes stubs - yours will differ
// I'm assuming your repository comes back with something like this - your exact case would be slightly different
class ProductEntity { 
public int Id {get;set;}
public string Model {get;set;}
public double Price {get;set;}
public string Currency {get;set;} // let's say your DB has a field that indicates what currency your price is in
}
class Product // depending on whether PoductType changes behaviour of the application, you might want to derive a couple classes based on that and override behaviours in respective subclasses rather than here
{
public int Id { get; set; }
public string model { get; set; }
public double Price { get; set; }
public string Type { get; set; }
}
#endregion
void Main()
{   
var currencies = new Dictionary<string, Currency> { { "AUD", new AUD() }, { "NZD", new NZD()}}; // you either hard-code these or read rates from the database. Using Dictionary is optional, I just found it easier to map later on
var products = new List<ProductEntity>(); // suppose, this is your new ProductBRepository().GetAll() call
var result = (from n in products
select new Product
{
Id = n.Id,
model = n.Model,
Price = currencies[n.Currency].ExchangeRate * n.Price, // a note of caution - you haven't specified the ORM you use or how you get your data, if ProductBRepository().GetAll() is a list it's probably all good, but EntityFramework might complain it's unable to translate this into SQL when building query for you
Type = "ProductB"
}).ToList();
}

这是一个相当高的水平,你肯定需要根据自己的需求进行调整,但希望它能为你提供我所处位置的一般概念。

UPD我还注意到您为不同类型的产品使用了两个存储库,这也是不可取的(除非产品实际上是完全不同的实体)。我建议您进行的Product类重构在这里会有所帮助:通过将两者合并为一个更通用的ProductRepository并应用Liskov替换原则,使您的代码不需要依赖于特定的repo-

相关内容

  • 没有找到相关文章

最新更新