重构基于多个参数进行计算的类的最佳设计模式
本文关键字:最佳 设计模式 计算 于多个 参数 重构 | 更新日期: 2023-09-27 18:10:42
我正在重构一组进行价格计算的类,如下所示。计算是根据许多参数进行的。代码是:
public interface IParcel {
int SourceCode { get; set; }
int DestinationCode { get; set; }
int weight{get;set;}
decimal CalculatePrice();
}
public abstract class GeneralParcel : IParcel {
//implementation of inteface properties
//these properties set with in SourceCode & DestinationCode
//and are used in CalculatePrice() inside classes that inherit from GeneralParcel
protected SourceProvinceCode{get; protected set;}
protected DestinationProvinceCode{get;protected set;}
//private variables we need for calculations
private static ReadOnlyDictionary<int, List<int>> _States_neighboureness;
private static ReadOnlyCollection<City> _Citieslist;
private static ReadOnlyCollection<Province> _Provinceslist;
protected ReadOnlyCollection<City> Citieslist {get { return _Citieslist; }}
protected ReadOnlyCollection<Province> ProvincesList {get { return _Provinceslist; }}
protected ReadOnlyDictionary<int, List<int>> StatesNeighboureness {get {return _States_neighboureness; }}
//constructor code that initializes the static variables
//implementation is in concrete classes
public abstract decimal CalculatePrice();
}
public ExpressParcel : GeneralParcel {
public decimal CalculatePrice() {
//use of those three static variables in calculations
// plus other properties & parameters
// for calculating prices
}
}
public SpecialParcel : GeneralParcel {
public decimal CalculatePrice() {
//use of those three static variables in calculations
// plus other properties & parameters
// for calculating prices
}
}
现在,代码有效地使用"策略模式"。
我的问题是,这三个静态属性实际上不是包裹对象的一部分,它们只需要用于价格计算,那么建议采用哪种设计模式或包装(重构)?
是否有如下需要的另一个接口(&然后把这些静态属性包装在里面?,甚至使静态类,因为它基本上只是一些计算),然后如何连接到IParcel?如何在 SpecialParcel
&中实现 CalculatePrice()
; ExpressParcel
classes?
public interface IPriceCalculator {
decimal CalculatePrice();
}
编辑:以上只是所有系统的一个大的图片,还有其他的考虑,在评论中,我们讨论他们,我再次写在这里清理东西。
有BulkDiscount
所有的ParcelTypes。当客户发送超过10个包裹(或任何阈值)时,就会发生批量邮寄,如果一个客户发送超过10个包裹到一个唯一的目的地(只有一个收件人),也会有特别折扣。现在这种类型的折扣管理在每个包裹类型的CalculatePrice()
。7公斤以下包裹的百叶窗也有折扣。
现在也有3个parceltype
,我在这里只显示其中的2个。但我们需要在未来增加其他类型(TNT &DHL支持)。每种类型都有许多服务,客户可以选择并付费。例如,sms service
或email service
&等等。
就我个人而言,虽然其他人可能会说包裹不应该知道如何计算自己的运输成本,但我不同意。你的设计已经确定有三种不同的包裹,有三种不同的计算方法,所以在我看来,物体应该有一个方法,例如CalculatePrice()
,这是完全合适的。
如果你真的想这样做,那么你需要IParcelPriceCalculator
的两个实现(或者你叫它什么)和GeneralParcel
上的一个抽象工厂方法来创建具体的ExpressParcelPriceCalculator
或SpecialParcelPriceCalculator
类。就我个人而言,我认为这是多余的,尤其是因为这些代码将与每个GeneralParcel
实现紧密耦合。
City
和Province
的静态集合分别设置为City
和Province
的公共静态属性。这样更整洁,如果我在维护代码,我希望在这里找到它们。StatesNeighbourliness
可能应该进入Province,或者它甚至可以证明它自己的类。
计算给定包裹价格的方式是不应该属于数据对象的职责。
考虑到您告诉我的,以下是我将如何实现,以尝试并考虑未来的考虑:
public interface IParcel {
int SourceCode { get; set; }
int DesinationCode { get; set; }
int Weight { get; set; }
}
public class PricingCondition {
//some conditions that you care about for calculations, maybe the amount of bulk or the date of the sale
//might possibly be just an enum depending on complexity
}
public static class PricingConditions {
public static readonly PricingCondition FourthOfJulyPricingCondition = new PricingCondition();
public static readonly PricingCondition BulkOrderPricingCondition = new PricingCondition();
//these could alternatively come from a database depending on your requirements
}
public interface IParcelBasePriceLookupService {
decimal GetBasePrice(IParcel parcel);
//probably some sort of caching
}
public class ParcelPriceCalculator {
IParcelBasePriceLookupService _basePriceLookupService;
decimal CalculatePrice(IParcel parcel, IEnumerable<PricingCondition> pricingConditions = new List<PricingCondition>()) {
//do some stuff
}
decimal CalculatePrice(IEnumerable<IParcel> parcels, IEnumerable<PricingCondition> pricingConditions = new List<PricingCondition>()) {
//do some stuff, probably a loop calling the first method
}
}
IPriceCalculator
将是单一责任原则的最佳实践。
但是将方法的签名改为decimal CalculatePrice(IParcel parcel);
该方法调用IParcel的CalculatePrice()方法来获取每个包裹的基本价格。
我提供的建议将在一定程度上取决于您如何生成和使用Parcel多态。我的意思是,我们看不到用什么标准来确定某件东西是"快递"还是"特殊",以及这些标准是否与包裹本身的属性或某些外部因素有关。
话虽如此,我认为你的直觉是一个很好的分离价格计算从包裹对象本身。正如kmkemp所指出的那样,一个包裹不应该根据它是什么类型的包裹来计算一个包裹的价格。一个包裹是一个数据传输/POCO类型的对象,至少在你给它一个重量、来源等时表明了这一点。
然而,我不清楚你为什么要使用这些接口。不要误解我的意思——接口对于解耦和可测试性来说是很好的,但是为什么除了带有抽象方法的抽象基类之外还要有包接口呢?就我个人而言,根据我所掌握的信息,我会这样做:
public class Parcel
{
int SourceCode { get; set; }
int DestinationCode { get; set; }
int weight { get; set; }
}
public abstract class GeneralCalculator
{
//Statics go here, or you can inject them as instance variables
//and they make sense here, since this is presumably data for price calculation
protected static ReadOnlyDictionary<int, List<int>> _States_neighboureness;
protected static ReadOnlyCollection<City> _Citieslist;
protected static ReadOnlyCollection<Province> _Provinceslist;
//.... etc
public abstract Decimal CalculatePrice(Parcel parcel);
}
public class ExpressCalculator : GeneralCalculator
{
public override decimal CalculatePrice(Parcel parcel)
{
return 0.0M;
}
}
public class SpecialCalculator : GeneralCalculator
{
public override decimal CalculatePrice(Parcel parcel)
{
return 0.0M;
}
}
但是,我不知道包裹实际上是如何处理的。根据生成和处理包裹的方式,您可能需要对这个概念进行某种修改。例如,如果包裹的类型取决于包裹的属性值,那么您可能需要定义一个工厂方法或类来接受包裹并返回计算器的适当实例。
但是,无论你如何修改它,我绝对会投票支持你将包裹的定义与计算其价格的方案解耦的想法。数据存储和数据处理是不同的关注点。我也不赞成在某个地方使用包含全局设置的静态类,但这是我个人的喜好——这类东西太容易获得setter而变成全局变量。
就像你说的,这些静态属性并不是真正的GeneralParcel
类的一部分。将它们移动到静态的"ListsOfThings"类中。
那么你可以使用引用ListsOfThings.ProvincesList
等的代码