用于重构基于许多参数进行计算的类的最佳设计模式

时间:2011-10-05 17:37:53

标签: c# design-patterns

我正在重构一组类,如下所示进行一些价格计算。 计算基于许多参数完成。 代码是:

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? 这样做,如何在 CalculatePrice() &amp;中实施 SpecialParcel ExpressParcel 类?

public interface IPriceCalculator {
    decimal CalculatePrice();
}
编辑:以上只是所有系统的一个大图,还有其他考虑因素在评论中,我们讨论它们,然后我再次在这里写清楚它们。

所有ParcelType都有BulkDiscount。当客户发送超过10个包裹(或任何阈值)时发生批量发布,当一个客户将超过10个包裹发送到唯一目的地(只有一个接收者)时,也会有特殊折扣。现在,在每个宗地类型CalculatePrice()中管理此类折扣。即使是低于7公斤包裹的百叶窗也有折扣。

现在也有3 parceltype,我在这里只显示其中的2个。但是我们将来需要添加其他类型(TNT和DHL支持)。 每种类型都有许多服务,客户可以选择并支付它。 例如,sms serviceemail service&amp;等等。

5 个答案:

答案 0 :(得分:3)

就个人而言,虽然其他人可能会说包裹不应该知道如何计算自己的运费,但我不同意。你的设计已经确定有三种不同类型的包裹有三种不同的计算方式,所以对于我的(幼稚的)眼睛来说,对象应该有一个方法,例如,这是完全合适的。 CalculatePrice()

如果你真的想这样做,那么你需要两个IParcelPriceCalculator(或者你称之为)的实现和GeneralParcel上的抽象工厂方法来创建具体的{{ 1}}或ExpressParcelPriceCalculator个类。就个人而言,我认为这有点过分,尤其是因为代码将与每个SpecialParcelPriceCalculator实现紧密耦合。

但我会考虑分别制作GeneralParcelCity的{​​{1}}和Province公共静态属性的静态集合。这只是更整洁,如果我维护代码,那就是我期望找到它们的地方。 City应该进入省,或者甚至可能为自己的班级辩护。

答案 1 :(得分:2)

您计算给定宗地的价格的方式是不属于数据对象的责任。

鉴于你告诉我的内容,我将采用以下方式来尝试并考虑未来的考虑因素:

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

答案 2 :(得分:1)

IPriceCalculator是单一责任原则的最佳做法。

但是将方法的签名更改为decimal CalculatePrice(IParcel parcel); 该方法调用IParcel的CalculatePrice()方法来获得每个宗地的基本价格。

答案 3 :(得分:1)

我提供的建议将取决于您如何生成和使用包裹多边形。我的意思是,我们无法看到用什么标准来确定某些东西是“快速”还是“特殊”,以及这些标准是否与包裹本身的属性或某些外部因素有关。

话虽如此,我认为你的直觉对于将价格计算与Parcel对象本身分开是一个很好的直觉。正如kmkemp指出的那样,一个包裹不应该根据包裹的类型来计算如何计算包裹的价格。包裹是数据传输/ POCO类型的对象,至少在您给它一个权重,来源等时所示。

但是,我不清楚为什么要使用这些接口。不要误会我的意思 - 接口对于解耦和可测试性很好,但为什么除了带抽象方法的抽象基类之外还有一个parcel接口。就个人而言,继续我所拥有的信息,我会这样做:

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

但是,我不知道包裹是如何被实际处理的。您可能需要对此概念进行某种修改,具体取决于您生成并处理宗地的方式。例如,如果包的类型取决于宗地的属性值,则可能需要定义一个工厂方法或类,它接受一个宗地并返回一个适当的计算器实例。

但是,无论你如何修改它,我肯定会投票支持你将包裹的定义与计算价格的方案脱钩的想法。数据存储和数据处理是另外的问题。我也不会投票支持包含全局设置的静态类,但这是我自己的个人品味 - 这种事情太容易获得制定者并成为一个全球变量。

答案 4 :(得分:0)

就像你说的那样,那些静态属性并不是GeneralParcel类的真正属性。将它们移动到静态“ListsOfThings”类。

然后您可以使用引用ListsOfThings.ProvincesList等的代码