我主张加强你的应用程序的打字。您的业务规则应该只使用强类型、经过验证的数据。他们不需要摆弄你的用户界面的怪癖。
让我们看一个假设情况的事实:用户必须输入汽车序列号。序列号的限制是它有 15 个字符,前两个是“VF”。
您的 UI 正在接受来自用户的一系列字符,无论是来自文本框还是控制台输入。在这个级别上,将这个字符序列存储到一个对象中是有意义的string
,因为它就是这样:一个字符序列。
相反,在业务规则方面,它应该已经过验证。实际上,只有有效string
s 的一个子集是 BR 感兴趣的。因此,您的 BR 应该操纵另一种不是string
. 在 C# 中,用户定义的类型是 aclass
或 a struct
。
所以让我们写这个类型。我选择创建一个class
,因为语法警告较少。
class CarSerialNumber {
}
汽车序列号在语义上是一个值,而不是一个对象。它具有价值语义。它的值可以存储到astring
中,我们要重新定义Equals(object)、GetHashCode(),并且它本身是equatable的,所以它可以实现IEquatable。
class CarSerialNumber : IEquatable<CarSerialNumber> {
string _value;
public override bool Equals(object right) {
if(this == right) {
return true;
}
if(right == null) {
return false;
}
if(!right is CarSerialNumber) {
return false;
}
return Equals((CarSerialNumber)right);
}
public override int GetHashCode() {
return _value.GetHashCode();
}
public bool Equals(CarSerialNumber right) {
return _value == right._value;
}
}
它应该用一个string
值构造,并且是不可变的。
class CarSerialNumber : IEquatable<CarSerialNumber> {
public CarSerialNumber(string value) {
_value = value;
}
readonly string _value;
// ... rest ....
}
在那里,我们刚刚找到了铰链。string
这个构造函数是从到的转折点CarSerialNumber
。此外,CarSerialNumber
不应使用无效字符串构造 a。由于类型系统此时无法保证参数string
是有效的汽车序列号,因此构造函数必须以异常方式退出。
因此,验证属于 RIGHT HERE。
class CarSerialNumber : IEquatable<CarSerialNumber> {
public CarSerialNumber(string value) {
Validate(value);
_value = value;
}
private Validate(string value) {
if(value == null) { // Never forget this one ;)
throw new ArgumentNullException();
}
// Rest of validation, throwing exceptions on failure.
}
// ... rest ....
}
您可能强烈希望考虑制作这种类型sealed
。此类类型的继承可能包括其他陷阱。(就像不相容子类型的神奇平等性)
在“更简单的类型可以做到”的地方创建此类类型的其他优点是:
- 当用作参数类型时,它记录了函数的期望,比参数名称更好。(或者至少,它是一个补充文档)
- 以这种方式创建的类型彼此不兼容。如果您尝试将 CarModel 传递给需要 CarSerialNumber 的函数,编译器会警告您。
enum
当您在编译时知道所有可能的值时,其他更强类型的解决方案包括使用s。