说我有一些代码,例如下面的代码:
public IEnumerable<KeyValuePair<int, int>> CalculateDenominationsFor(int cost)
{
var target = cost;
foreach (var denomination in currency.AvailableDenominations.OrderByDescending(a => a))
{
var numberRequired = target / denomination;
if (numberRequired >= 1)
{
yield return new KeyValuePair<int, int>(denomination, numberRequired);
}
target = target - (numberRequired * denomination);
}
}
这是一个域服务。我将如何验证成本参数?我是否必须将其封装在值对象中,然后将验证放入值对象构造函数?
中?例如,我想确保成本大于零,并且只有我与Sterling合作时有两个小数。
我是否必须将其封装在值对象中,然后将验证放入值对象构造函数?
中
这是理想的选择,因为无论使用该域对象的其他地方,您都知道它不在无效状态下,因为它的构造函数使得这是不可能的。
作为一个非常粗略的例子,它看起来像这样:
public class Cost
{
public Cost(decimal amount)
{
var rounded = decimal.Round(amount, 2);
if(rounded <= 0m)
throw new ArgumentOutOfRangeException(nameof(amount), $"{nameof(amount)} must be greater than zero when rounded to two decimal places.");
Amount = rounded;
}
public decimal Amount { get; }
}
您如何进行围绕的细节可能会有所不同。但关键是,它发生在从您的域外部收到值时进行的,并确保您的Cost
类的每个实例在使用其他地方都有效,并且是不可能的。
也可能值得使用用于Money
类型的许多Nuget软件包之一,而不是使用decimal
。这样,如果价格是特定货币,您可以明确。而且,如果您处理多种货币,您将不会头痛地调整您的代码。
您还可以实现IComparable<Cost>
和IEquatable<Cost>
,以便您可以直接比较Cost
的实例,而不必比较其Amount
。
这只是我使用的金钱类型的片段。nuget,github
public struct Money : IComparable<Money>, IComparable, IXmlSerializable
{
public Money(decimal amount, Currency currency)
{
Amount = amount;
Currency = currency;
}
public decimal Amount { get; private set; }
public Currency Currency { get; private set; }
public static Money Round(Money m)
{
return new Money(decimal.Round(m.Amount), m.Currency);
}
}
如果您正在使用类似的东西,那么您的 Cost
可能看起来像这样:
public class Cost
{
public Cost(Money amount)
{
if(amount.Currency != Currency.GBP)
throw new ArgumentException("The currency type must be GPB.");
var rounded = Money.Round(amount, 2);
if(rounded.Amount <= 0m)
throw new ArgumentOutOfRangeException(nameof(amount), $"{nameof(amount)} must be greater than zero when rounded to two decimal places.");
Amount = rounded;
}
public Money Amount { get; }
}