如何建模此发票要求

how to model this invoice requirements

在一些业务领域,我收到的开发票要求如下:

+ Invoice Items are of two types: a service or a fee.
    - Invoice Service Item is taxable. fee is not.
+ Invoice Items (services and fees) cost is calculated in two manners: 
    - As a whole: the service or fee has a fixed cost.
    - By Individual: the cost is multiplied by Individuals count.

我一直在阅读设计模式、领域驱动设计、继承和多态性,我想实践我在这个新项目中学到的东西。 我想为 Invoicing 子域提供 model,它捕获域无处不在的语言。我来过这个模型,但感觉不对!

当您为对象建模时,请尝试考虑它的行为,而不仅仅是考虑您需要存储的数据。专注于数据通常会导致贫血的数据模型。如果你需要计算成本和税收,像这样的东西怎么样?

internal class InvoiceItem : ValueObject
{
    private readonly string _description;
    private readonly decimal _cost;
    private readonly int _amount;

    private InvoiceItem(string description, decimal cost, int amount)
    {
        _description = description;
        _cost = cost;
        _amount = amount;
    }

    public decimal TotalCost()
    {
        return _cost * _amount;
    }

    public decimal Tax(ITaxCalculationPolicy taxCalculationPolicy)
    {
        return taxCalculationPolicy.CalculateTax(TotalCost());
    }

    public static InvoiceItem ByWholeInvoiceItem(string description, decimal cost)
    {
        return new InvoiceItem(description, cost, 1);
    }

    public static InvoiceItem ByIndividualInvoiceItem(string description, decimal cost, int amount)
    {
        return new InvoiceItem(description, cost, amount);
    }
}

internal interface ITaxCalculationPolicy
{
    decimal CalculateTax(decimal cost);
}

internal class ServiceTaxPolicy : ITaxCalculationPolicy
{
    private const decimal TaxPercent = 0.18m;

    public decimal CalculateTax(decimal cost)
    {
        return cost * TaxPercent;
    }
}

internal class FeeTaxPolicy : ITaxCalculationPolicy
{
    private const decimal TaxValue = 0;

    public decimal CalculateTax(decimal cost)
    {
        return TaxValue;
    }
}

如果您不希望客户端决定应该使用哪种 ITaxCalculationPolicy,您也可以尝试将某种 ITaxCalculationPolicyFactory 传递给 Tax() 方法。比起您必须在其中存储一种发票项目并在计算税收时将其传递给该工厂。

我的建议是尽可能避免域模型中的继承。您 可能 经常遇到继承用例,但与 UL 相比,它可能更多地涉及技术实现细节。

我也避免将 classification 设计为结构,因为它会降低建模的灵活性。结构应该反映真正不会改变的 "fixed" 位,例如 Invoice/InvoiceItemtype 发票项目似乎适合 classification。这些东西可能很难发现,但是当你发生 class 爆炸时,它可能是一个危险信号。一旦领域专家开始 概念分解为"types",那么您可能正在查看某种class化结构。

此外,避免将服务注入域对象。您可以选择 double-dispatch,其中将实现服务接口的服务注入到相关方法中,并且该方法调用该服务以获取相关税费。我更喜欢的另一种方法是传递方法所需的值。

请记住,不会 的技术实施细节将成为 UL 的一部分,因此不必担心 UL 的每个部分都有代表作为 class。只要您以某种形式捕获了 UL 的对象行为和形状,并且可以完整地表示 UL,那么您应该没有任何问题。