0

设计问题...

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

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

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

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

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

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

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

提前致谢。

4

2 回答 2

0

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

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

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

相反,在业务规则方面,它应该已经过验证。实际上,只有有效strings 的一个子集是 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。

于 2013-05-17T13:50:59.453 回答
0

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

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

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

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

于 2012-09-22T00:01:56.430 回答