2

这个问题类似于这个问题的(一个子集

在这种情况下,它使用运行时类型来区分成功和失败返回的结果。

我经常看到以下模式:

public struct Result {
    public boolean IsSuccess { get;set;}
    public string ErrorMessage {get;set;}
    public int Value {get;set;}
}
...
Result result = someObject.SomeMethod();
if (result.IsSuccess) DoSomething(result.Value);
else handleError(result.ErrorMessage);

我认为以下更自然,并且更清楚地表达了意图(在我看来):

public abstract class Result { }
public sealed class Failure : Result {
    public string ErrorMessage { get; set; }
}
public sealed class Success : Result {
    public int Value { get; set; }
}
...
Result result = someObject.SomeMethod();
if (result is Success) DoSomething((result as Success).Value);
else if (result is Failure) handleError((result as Failure).ErrorMessage);

另请注意,.Net(和许多其他语言)在带有多个 catch 子句的 try-catch 块中使用此模式(其中异常类型选择一个 catch 块)。

编辑:这种模式(即依赖运行时类型)与 F# 的可区分联合相同,不同之处在于在 F# 中它是本机的,而在 C# 中它是使用用于不同目的的构造来模拟的。

编辑:我认为我的第一个代码的主要问题是代码气味“部分初始化的对象”。在 100% 的情况下,只有一半的对象会被初始化。它也几乎违反了 ISP,“几乎”因为一旦 .IsSuccess 被评估,以后只会使用对象的一部分(如果成功,则仅使用 .Result,如果是错误 - 仅使用错误属性)。运行类型检查解决方案没有这些问题。

所以问题是:使用这种模式有什么问题?我对以下几个方面的问题特别感兴趣:可维护性、可读性、可测试性、概念纯度和 OOP/OOD。

4

3 回答 3

3

作为一般规则,我更喜欢第一种形式。

第二个示例需要更多击键并添加 4 种类型检查操作。这对消费者来说也不太明显(你不能混淆IsSuccess属性的含义)。仅仅为了确定操作的结果而必须执行类型转换似乎不太合乎逻辑(似乎类似于对程序流使用异常;您可以这样做,但您不应该这样做)。

此外,第一种形式可以用作描述数据的独立于平台的机制。第二个不能。换句话说,您可以轻松地序列化由原语组成的结果类型并与任何消费者共享,但需要类型检查限制/消除这种可能性。

为了提高Result对象的质量,您可以创建一个Messages集合,其中包含的不仅仅是一个字符串(可能包括代码、严重性、消息等)。这允许您的所有结果返回消息,而不仅仅是失败的操作。如果操作失败,您可能会假设它Messages至少包含一条错误消息(和/或使用 LINQ 查询集合以查找所有与错误相关的消息)。

于 2012-05-23T06:17:32.060 回答
0

第二种形式错误的学术原因如下:

C# 作为一种 OO 语言,其设计假设将用于对底层系统的类别(例如业务概念)进行建模,而实例将用于对事实进行建模。

使用对事实建模是对工具的滥用。

虽然我仍然不知道实际的后果是什么。

于 2013-09-10T16:43:19.597 回答
0

面向对象的设计对一次性信息并不重要。这个结果对象的生命周期非常有限,因此属于堆栈上的结构。

如果您想谈论维护/可读性,那么人们最初会对此代码感到困惑。

如果您想在错误传播上采用面向对象的方式,那么纯粹使用异常(尽管这会稍微降低这些用例的性能)。

于 2013-09-10T17:48:30.087 回答