设计问题......
我的任务是在C#中创建一个小型控制台应用程序,并希望遵循最佳的封装实践。
我有UI层,控制器/命令处理器层,业务规则层和数据访问层。
该应用程序允许用户创建记录,填写一些字段,然后将其插入到内存中以及其余记录。
假设其中一些字段的有效选项数量有限,那么验证用户输入是否符合其中一个选项的最合理位置在哪里?
设置一个存在于UI中的临时记录是好的设计,当我使用用户数据并检查记录类中的setter方法的返回值时填写它?我的记录类是否应该实现验证,而是验证用户输入是否与业务规则层中的选项匹配?
如果这会影响答案,那么应用程序就是用C#编写的。我目前的想法是使用记录类中的属性并在UI中具有临时记录,然后仅在用户输入与选项匹配时才在记录类中设置基础成员。然后,UI将使用get方法将用户输入与记录中的值进行比较,并在值不匹配时提示用户重新输入数据(该集合未成功。)
是否有更好的方法遵循更好的设计原则?
提前致谢。
答案 0 :(得分:0)
如果我理解正确的布局,我会推荐这样的东西:
控制器/命令处理器将验证命令,确保它们遵循适当的语法。
业务规则层将确保输入的所有数据都是有效数据。在将数据输入记录之前,它将被验证。
如果您有数据连接命令,数据访问层将确保输入的连接信息有效。
答案 1 :(得分:0)
我主张加强您的应用程序的输入。您的业务规则应仅使用强类型,经过验证的数据。他们不需要弄乱UI的怪癖。
让我们看看假设情况的事实:用户必须输入汽车序列号。对序列号的限制是它有15个字符,前两个是“VF”。
您的UI正在接受来自用户的一系列字符,无论是来自TextBox还是控制台输入。在这个级别,将这个字符序列存储到string
对象中是有意义的,因为它就是这样的:一系列字符。
相反,在业务规则方面,它应该已经过验证。实际上,BR中只有一部分有效string
感兴趣。因此,您的BR应该操纵另一种不是string
的类型。 C#中的用户定义类型是class
或struct
。
所以让我们写这种类型。我选择创建一个class
,因为句法警告较少。
class CarSerialNumber {
}
汽车序列号在语义上是一个值,而不是一个对象。它具有价值语义。它的值可以存储到string
中,我们必须重新定义Equals(object),GetHashCode(),它本身是等价的,所以它可以实现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
不应使用无效字符串构造。由于类型系统此时无法保证参数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
。此类类型的继承可能包括其他陷阱。 (就像不相容的亚型的神奇等同性一样)
创建此类“类型更简单的类型”的其他优点是:
更强类型的其他解决方案包括在编译时知道所有可能的值时使用enum
。