面向对象控制台应用程序中的输入验证,它属于哪里



设计问题。。。

我的任务是用C#创建一个小型控制台应用程序,并希望遵循最佳封装实践。

我有一个UI层、控制器/命令处理器层、业务规则层和数据访问层。

该应用程序允许用户创建一条记录,填写一些字段,然后将其与其他记录一起插入内存。

假设其中一些字段的有效选项数量有限,那么验证用户输入是否与其中一个选项匹配的最合乎逻辑的地方在哪里?

在UI中有一种临时记录,在处理用户数据时填写它,并检查记录类中setter方法的返回值,这是一个好的设计吗?我的记录类是否应该不实现验证,而是验证用户输入是否与业务规则层中的选项匹配?

应用程序是用C#编写的,如果这会影响答案的话。我目前的想法是在记录类中使用属性,并在UI中具有临时记录,然后仅在用户输入与选项匹配的情况下在记录类设置基础成员。然后,UI将使用get方法将用户输入与记录中的值进行比较,如果值不匹配(设置不成功),则提示用户重新输入数据

有没有更好的方法遵循更好的设计原则?

提前谢谢。

如果我正确理解布局,我会推荐这样的东西:

控制器/命令处理器将验证命令,确保它们遵循适当的语法。

业务规则层将确保输入的所有数据都是有效数据。在将数据输入记录之前,将对其进行验证。

如果有数据连接命令,数据访问层将确保输入的连接信息有效。

我主张加强应用程序的类型。您的业务规则应该只使用经过验证的强类型数据。他们不需要摆弄用户界面的怪癖。

让我们来看看一个假设情况的事实:用户必须输入一个汽车序列号。序列号的限制条件是它有15个字符,前两个字符是"VF"。

您的UI正在接受来自用户的一系列字符,无论是来自TextBox还是控制台输入。在这个级别上,将这个字符序列存储到string对象中是有意义的,因为它就是这样的:一个字符序列。

相反,在业务规则方面,它应该已经得到验证。实际上,只有有效string的子集对BR感兴趣。因此,BR应该操作另一个不是string的类型。C#中的用户定义类型是classstruct

所以让我们写这个类型。我选择创建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 ....
}

在那里我们刚刚找到了铰链。这个构造函数是从stringCarSerialNumber的转折点。此外,CarSerialNumber不应使用无效字符串进行构造。由于类型系统在这一点上不能保证参数string是有效的汽车序列号,因此构造函数必须例外。

因此,验证就在这里。

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的函数,编译器将发出警告

其他更强类型的解决方案包括在编译时知道所有可能的值时使用enums。

最新更新