十一、了解操作结果设计模式

在本章中,我们将从简单到更复杂的案例探索操作结果模式。操作结果旨在向调用者传达操作的成功或失败。它还允许该操作向调用者返回一个值和一条或多条消息。

想象一下,在任何系统中,您都希望显示用户友好的错误消息,获得一些小的速度增益,甚至可以轻松明确地处理故障。操作结果设计模式可以帮助您实现这些目标。使用它的一种方法是作为远程操作的结果,例如在查询远程 web 服务之后。

本章将介绍以下主题:

  • 操作结果设计模式基础
  • 返回值的操作结果设计模式
  • 返回错误消息的操作结果设计模式
  • 返回具有严重性级别的消息的操作结果设计模式
  • 使用子类和静态工厂方法更好地隔离成功和失败

目标

操作结果模式的作用是为操作(方法)提供返回复杂结果(对象)的可能性,这允许消费者:

  • [必须]访问操作的成功指示器(即操作是否成功)。
  • [可选]如果存在操作结果(方法的返回值),则访问操作结果。
  • [可选]在操作未成功的情况下,访问故障原因(错误消息)。
  • [可选]访问记录操作结果的其他信息。这可以是简单的消息列表,也可以是复杂的多个属性。

这可以更进一步,例如返回故障的严重性或为特定用例添加任何其他相关信息。成功指示器可以是二进制(truefalse,也可以有两种以上的状态,如成功、部分成功和失败。你的想象力(和需求)是你的极限!

提示

首先关注你的需求,然后运用你的想象力找到最好的解决方案。软件工程不仅仅是应用别人告诉你的技术。这是一门艺术!唯一的区别是你正在制作软件,而不是绘画或木工。而且大多数人不会看到这些艺术(代码)。

设计

当操作失败时,很容易依赖于抛出异常。但是,当您不想或不能使用异常时,操作结果模式是组件之间通信成功或失败的另一种方式。

为了有效地使用,方法必须返回一个包含目标部分中显示的一个或多个元素的对象。根据经验,返回操作结果的方法不应引发异常。通过这种方式,用户不必处理操作结果本身以外的任何事情。对于特殊情况,您可以允许抛出异常,但在这一点上,这将是一个基于明确规范的判断调用或面临实际问题。

在看了描述此模式最简单形式的基本序列图(适用于所有示例)后,让我们跳入代码并探索多个较小的示例,而不是遍历所有可能的 UML 图:

图 11.1–运行结果设计模式的序列图

正如我们从图中看到的,一个操作返回一个结果(一个对象),然后调用方可以处理该结果。下面的示例介绍了结果对象中可以包含的内容。

项目-实施不同的操作结果模式

在这个项目中,使用者将 HTTP 请求路由到正确的处理程序。我们正在逐个访问这些处理程序,这将帮助我们实现从简单到更复杂的操作结果。这将向您展示实现操作结果模式的许多可选方法,以帮助您理解它,使其成为您自己的模式,并根据项目的需要实现它。

消费者

所有示例中的消费者都是Startup类。以下代码将请求路由到处理程序:

app.UseRouter(builder =>
{
    builder.MapGet("/simplest-form", SimplestFormHandler);
    builder.MapGet("/single-error", SingleErrorHandler);
    builder.MapGet("/single-error-with-value", SingleErrorWithValueHandler);
    builder.MapGet("/multiple-errors-with-value", MultipleErrorsWithValueHandler);
    builder.MapGet("/multiple-errors-with-value-and-severity", MultipleErrorsWithValueAndSeverityHandler);
    builder.MapGet("/static-factory-methods", StaticFactoryMethodHandler);
});

接下来,我们逐一介绍每个处理程序。

最简单的形式

下图表示操作结果模式的最简单形式:

Figure 11.2 – Class diagram of the Operation Result design pattern

图 11.2–运行结果设计模式的类图

我们可以将该类图转换为以下代码块:

namespace OperationResult
{
    public class Startup
    {
        // ...
        private async Task SimplestFormHandler(HttpRequest request, HttpResponse response, RouteData data)
        {
            // Create an instance of the class that contains the operation
            var executor = new SimplestForm.Executor();
            // Execute the operation and handle its result
            var result = executor.Operation();
            if (result.Succeeded)
            {
                // Handle the success
                await response.WriteAsync("Operation succeeded");
            }
            else
            {
                // Handle the failure
                await response.WriteAsync("Operation failed");
            }
        }
    }
}

前面的代码处理/simplest-formHTTP 请求。它是操作的消费者。

namespace OperationResult.SimplestForm
{
    public class Executor
    {
        public OperationResult Operation()
        {
            // Randomize the success indicator
            // This should be real logic
            var randomNumber = new Random().Next(100);
            var success = randomNumber % 2 == 0;
            // Return the operation result
            return new OperationResult(success);
        }
    }
    public class OperationResult
    {
        public OperationResult(bool succeeded)
        {
            Succeeded = succeeded;
        }
        public bool Succeeded { get; }
    }
}

Executor类使用Operation方法实现要执行的操作。该方法返回OperationResult类的一个实例。实现基于随机数。有时成功,有时失败。您通常会在该方法中编写一些应用逻辑。

OperationResult类表示操作的结果。在本例中,它是一个简单的只读布尔值,存储在Succeeded属性中。

在这种形式中,Operation()方法返回boolOperationResult实例之间的差异很小,但仍然存在。通过返回一个OperationResult对象,您可以随着时间的推移扩展返回值,并将其添加到bool对象中,这在不更新所有使用者的情况下是无法实现的。

单一错误消息

现在我们知道手术是否成功,我们想知道哪里出了问题。为此,我们可以向OperationResult类添加一个ErrorMessage属性。通过这样做,我们不再需要设置操作是否成功;我们可以用ErrorMessage属性来计算。

这一改进背后的逻辑如下:

  • 没有错误消息时,操作成功。
  • 出现错误消息时,操作失败。

实现此逻辑的OperationResult如下所示:

namespace OperationResult.SingleError
{
    public class OperationResult
    {
        public OperationResult() { }
        public OperationResult(string errorMessage)
        {
            ErrorMessage = errorMessage ?? throw new ArgumentNullException(nameof(errorMessage));
        }
        public bool Succeeded => string.IsNullOrWhiteSpace(ErrorMessage);
        public string ErrorMessage { get; }
    }
}

在前面的代码中,我们有以下内容:

  • Two constructors:

    a) 处理成功的无参数构造函数。

    b) 将错误消息作为处理错误的参数的构造函数。

  • 检查ErrorMessageSucceeded属性。

  • 包含可选错误消息的ErrorMessage属性。

该操作的执行者看起来类似,但使用新的构造函数,设置错误消息,而不是直接设置成功指示器:

namespace OperationResult.SingleError
{
    public class Executor
    {
        public OperationResult Operation()
        {
            // Randomize the success indicator
            // This should be real logic
            var randomNumber = new Random().Next(100);
            var success = randomNumber % 2 == 0;
            // Return the operation result
            return success 
 ? new OperationResult() 
 : new OperationResult($"Something went wrong with the number '{randomNumber}'.");
        }
    }
}

消费代码与上一个示例中相同,但在响应输出中写入错误消息,而不是一般故障字符串:

namespace OperationResult
{
    public class Startup
    {
        // ...
        private async Task SingleErrorHandler(HttpRequest request, HttpResponse response, RouteData data)
        {
            // Create an instance of the class that contains the operation
            var executor = new SingleError.Executor();
            // Execute the operation and handle its result
            var result = executor.Operation();
            if (result.Succeeded)
            {
                // Handle the success
                await response.WriteAsync("Operation succeeded");
            }
            else
            {
                // Handle the failure
 await response.WriteAsync (result.ErrorMessage);
            }
        }
    }
}

在查看该示例时,我们可以开始理解操作结果模式的有用性。它使我们更远离简单的成功指标,它看起来像一个过于复杂的布尔值。这并不是我们探索的终点,因为可以在更复杂的场景中设计和使用更多的表单。

增加一个返回值

既然我们有了失败的原因,我们可能希望操作返回一个值。为了实现这一点,让我们从上一个示例开始,在OperationResult类中添加一个Value属性,如下所示(我们在第 17 章ASP.NET Core 用户界面中介绍了仅初始化属性

namespace OperationResult.SingleErrorWithValue
{
    public class OperationResult
    {
        // ...
        public int? Value { get; init; }
    }
}

操作也非常类似,但我们正在使用对象初始值设定项设置Value

namespace OperationResult.SingleErrorWithValue
{
    public class Executor
    {
        public OperationResult Operation()
        {
            // Randomize the success indicator
            // This should be real logic
            var randomNumber = new Random().Next(100);
            var success = randomNumber % 2 == 0;meet
            // Return the operation result
            return success
                ? new OperationResult { Value = 
 randomNumber }
                : new OperationResult($"Something went wrong with the number '{randomNumber}'.")
 {
 Value = randomNumber
 };
        }
    }
}

有了后,消费者可以按如下方式使用Value

namespace OperationResult
{
    public class Startup
    {
        // ...
        private async Task SingleErrorWithValueHandler (HttpRequest request, HttpResponse response, RouteData data)
        {
            // Create an instance of the class that contains the operation
            var executor = new SingleErrorWithValue.Executor();
            // Execute the operation and handle its result
            var result = executor.Operation();
            if (result.Succeeded)
            {
                // Handle the success
 await response.WriteAsync($"Operation succeeded with a value of '{result.Value} '.");
            }
            else
            {
                // Handle the failure
                await response.WriteAsync (result.ErrorMessage);
            }
        }
    }
}

从这个示例中我们可以看到,当操作失败时,我们可以显示相关的错误消息,当操作成功时(甚至在这种情况下失败时),我们可以使用返回值,所有这些都不会引发异常。有了这一点,操作结果模式的力量开始显现。我们还没有完成,所以让我们跳到下一个进化。

多条错误消息

现在我们已经到了可以将ValueErrorMessage传输给操作使用者的地步,但是传输多个错误(例如验证错误)呢?为此,我们可以将我们的ErrorMessage属性转换为IEnumerable<string>并添加管理消息的方法:

namespace OperationResult.MultipleErrorsWithValue
{
    public class OperationResult
    {
        private readonly List<string> _errors;
        public OperationResult(params string[] errors)
        {
            _errors = new List<string>(errors ?? Enumerable.Empty<string>());
        }
        public bool Succeeded => !HasErrors();
        public int? Value { get; set; }
        public IEnumerable<string> Errors => new ReadOnlyCollection<string>(_errors);
        public bool HasErrors()
        {
            return Errors?.Count() > 0;
        }
        public void AddError(string message)
        {
            _errors.Add(message);
        }
    }
}

让我们先看看前面的代码中的新部分,然后再继续:

  • 错误现在存储在List<string> _errors中,并通过IEnumerable<string>接口下隐藏的ReadOnlyCollection<string>实例返回给消费者。ReadOnlyCollection<string>实例拒绝从外部更改集合,例如,假设消费者足够聪明,可以将IEnumerable<string>转换为List<string>
  • Succeeded属性已更新,以说明集合而不是单个消息,并遵循相同的逻辑。
  • 为方便起见,增加了HasErrors方法。
  • The AddError method allows adding errors after the instance creation, which could happen in more complex scenarios, such as multi-step operations where parts could fail without the operation itself failing.

    笔记

    对于结果的额外控制,AddError方法应隐藏在操作外部。这样,使用者就不能在操作完成后向结果中添加错误。当然,所需的控制级别取决于每个特定场景。对此进行编码的一种方法是返回一个不包含该方法的接口,而不是返回包含该方法的具体类型。

现在更新了操作结果,操作本身可以保持不变,但我们可以在结果中添加多个错误。消费者必须处理这一细微差异,并支持多个错误。

让我们来看看这个代码:

namespace OperationResult
{
    public class Startup
    {
        private async Task MultipleErrorsWithValueHandler(HttpRequest request, HttpResponse response, RouteData data)
        {
            // Create an instance of the class that contains the operation
            var executor = new MultipleErrorsWithValue.Executor();
            // Execute the operation and handle its result
            var result = executor.Operation();
            if (result.Succeeded)
            {
                // Handle the success
                await response.WriteAsync($"Operation succeeded with a value of '{result.Value}'.");
            }
            else
            {
                // Handle the failure
 var json = JsonSerializer.Serialize (result.Errors);
                response.Headers["ContentType"] = "application/json";
                await response.WriteAsync(json);
            }
        }
    }
}

代码将IEnumerable<string> Errors属性序列化为 JSON,然后将其输出到客户端,以帮助可视化集合。

提示

当操作成功时返回一个plain/text字符串,当操作失败时返回一个application/json数组通常不是一个好主意。我建议不要在实际应用中执行类似的操作。返回 JSON 或纯文本。尽量不要在单个端点中混合内容类型。在大多数情况下,混合内容类型只会产生可避免的复杂性。我们可以说,读取content-type和状态码头就足以知道服务器返回了什么,这就是 HTTP 规范中这些头的用途。但是,即使这是真的,您的开发伙伴在使用您的 API 时总是能够期望相同的内容类型也要容易得多。

在设计系统契约时,一致性和一致性通常优于不一致性、模糊性和差异性。

我们的操作结果模式实现越来越好,但仍然缺少一些特性。这些特性之一是传播非错误消息的可能性,例如信息消息和警告,我们将在下一步实现这些功能。

增加消息严重性

既然我们的操作结果结构已经具体化,让我们更新我们的上一次迭代以支持消息严重性。

首先,我们需要一个严重性指标。安是这类工作的好人选,但可能是别的。让我们把它命名为OperationResultSeverity

然后我们需要一个消息类来封装消息和严重性级别;让我们把那个类命名为OperationResultMessage。新代码如下所示:

namespace OperationResult.WithSeverity
{
    public class OperationResultMessage
    {
        public OperationResultMessage(string message, OperationResultSeverity severity)
        {
            Message = message ?? throw new ArgumentNullException(nameof(message));
            Severity = severity;
        }
        public string Message { get; }
        public OperationResultSeverity Severity { get; }
    }
    public enum OperationResultSeverity
    {
        Information = 0,
        Warning = 1,
        Error = 2
    }
}

正如您所看到的,我们有一个简单的数据结构来替换我们的string消息。

然后我们需要更新OperationResult类以使用新的OperationResultMessage类。我们需要确保只有在没有OperationResultSeverity.Error的情况下,操作结果才显示成功,允许其传输OperationResultSeverity.InformationOperationResultSeverity.Warnings消息:

namespace OperationResult.WithSeverity
{
    public class OperationResult
    {
        private readonly List<OperationResultMessage> _messages;
        public OperationResult(params OperationResultMessage[] errors)
        {
            _messages = new List<OperationResultMessage> (errors ?? Enumerable.Empty <OperationResultMessage>());
        }
        public bool Succeeded => !HasErrors();
        public int? Value { get; init; }
        public IEnumerable<OperationResultMessage> Messages
            => new ReadOnlyCollection <OperationResultMessage>(_messages);
        public bool HasErrors()
        {
 return FindErrors().Count() > 0;
        }
        public void AddMessage(OperationResultMessage message)
        {
            _messages.Add(message);
        }
 private IEnumerable<OperationResultMessage> FindErrors()
 => _messages.Where(x => x.Severity == OperationResultSeverity.Error);
    }
}

高亮显示的行表示仅当_messages列表中不存在错误时设置成功状态的新逻辑。

有了这一点,Executor类也需要改进。

那么让我们看看Executor类的新版本:

namespace OperationResult.WithSeverity
{
    public class Executor
    {
        public OperationResult Operation()
        {
            // Randomize the success indicator
            // This should be real logic
            var randomNumber = new Random().Next(100);
            var success = randomNumber % 2 == 0;
            // Some information message
            var information = new OperationResultMessage(
                "This should be very informative!",
                OperationResultSeverity.Information
            );
            // Return the operation result
            if (success)
            {
                var warning = new OperationResultMessage(
                    "Something went wrong, but we will try again later automatically until it works!",
                    OperationResultSeverity.Warning
                );
                return new OperationResult(information, warning) { Value = randomNumber };
            }
            else
            {
                var error = new OperationResultMessage(
                    $"Something went wrong with the number '{randomNumber}'.",
                    OperationResultSeverity.Error
                );
                return new OperationResult(information, error) { Value = randomNumber };
            }
        }
    }
}

正如您可能已经注意到的,我们删除了第三个操作符,以使代码更易于阅读。

提示

您应该始终致力于编写易于阅读的代码。使用语言特性是可以的,但是将语句嵌套在单行语句之上有其局限性,很快就会变得一团糟。

在最后一个代码块中,成功和失败都返回两条消息:

  • 成功时,消息为信息消息和警告。
  • 如果失败,则消息为信息消息和错误。

从使用者的角度(请参阅下面的代码),我们现在只将结果序列化到输出,以清楚地显示结果。以下是使用此新操作的/multiple-errors-with-value-and-severity endpoint委托:

namespace OperationResult
{
    public class Startup
    {
        // ...
        private async Task MultipleErrorsWithValueAndSeverityHandler(HttpRequest request, HttpResponse response, RouteData data)
        {
            // Create an instance of the class that contains the operation
            var executor = new WithSeverity.Executor();
            // Execute the operation and handle its result
            var result = executor.Operation();
            if (result.Succeeded)
            {
                // Handle the success
            }
            else
            {
                // Handle the failure
            }
            var json = JsonSerializer.Serialize(result);
            response.Headers["ContentType"] = "application/json";
            await response.WriteAsync(json);
        }
    }
}

正如您所看到的,它仍然很容易使用,但现在增加了更多的灵活性。我们可以处理不同类型的消息,例如向用户显示消息、重试操作等等。

目前,如果运行应用并调用该端点,成功的调用将返回一个 JSON 字符串,如下所示:

{
    "Succeeded": true,
    "Value": 86,
    "Messages": [
        {
            "Message": "This should be very informative!",
            "Severity": 0
        },
        {
            "Message": "Something went wrong, but we will try again later automatically until it works!",
            "Severity": 1
        }
    ]
}

失败应返回如下所示的 JSON 字符串:

{
    "Succeeded": false,
    "Value": 87,
    "Messages": [
        {
            "Message": "This should be very informative!",
            "Severity": 0
        },
        {
            "Message": "Something went wrong with the 
             number '87'.",
            "Severity": 2
        }
    ]
}

改进此设计的另一个想法是添加一个Status属性,该属性根据每条消息的严重性级别返回复杂的成功结果。为此,我们可以创建另一个enum

public enum OperationStatus{ Success, Failure, PartialSuccess}

然后我们可以通过OperationResult类上名为Status的新属性访问它。有了这一点,消费者可以处理部分成功,而无需深入挖掘信息本身。我会让你自己玩这个。

现在我们已经将我们的简单示例扩展到这里,如果我们希望Value是可选的,会发生什么?为此,我们可以创建多个操作结果类,每个类都包含或多或少的信息(属性);让我们下一步试试。

子类和工厂

在这个迭代中,我们保留了所有的属性,但是我们改变了实例化OperationResult对象的方式。

一个静态工厂方法只不过是一个负责创建对象的静态方法。正如您将要看到的,它可以变得方便易用。和往常一样,我再怎么强调也不过分:在设计静态的东西时要小心,否则以后它可能会困扰你。

让我们从一些已经访问过的代码开始:

namespace OperationResult.StaticFactoryMethod
{
    public class OperationResultMessage
    {
        public OperationResultMessage(string message, OperationResultSeverity severity)
        {
            Message = message ?? throw new ArgumentNullException(nameof(message));
            Severity = severity;
        }
        public string Message { get; }
        public OperationResultSeverity Severity { get; }
    }
    public enum OperationResultSeverity
    {
        Information = 0,
        Warning = 1,
        Error = 2
    }
}

前面的代码与我们以前使用的代码相同。在下面的代码块中,计算操作的成功或失败结果时不考虑严重性。相反,我们创建了一个包含两个子类的抽象OperationResult类:

  • SuccessfulOperationResult,表示操作成功。
  • FailedOperationResult,表示操作失败。

然后下一步是通过创建两个静态工厂方法来强制使用专门设计的类:

  • public static OperationResult Success(),返回一个SuccessfulOperationResult
  • public static OperationResult Failure(params OperationResultMessage[] errors),返回一个FailedOperationResult

这样做将决定操作是否成功的责任从OperationResult类本身转移到Operation方法。

以下代码块显示了新的OperationResult实现(静态工厂突出显示):

namespace OperationResult.StaticFactoryMethod
{
    public abstract class OperationResult
    {
        private OperationResult() { }
        public abstract bool Succeeded { get; }
        public virtual int? Value { get; init; }
        public abstract IEnumerable<OperationResultMessage> Messages { get; }
 public static OperationResult Success(int? value = null)
 {
 return new SuccessfulOperationResult { Value = value };
 }
 public static OperationResult Failure(params OperationResultMessage[] errors)
 {
 return new FailedOperationResult(errors);
 }
        public sealed class SuccessfulOperationResult : OperationResult
        {
            public override bool Succeeded => true;
            public override IEnumerable <OperationResultMessage> Messages 
                => Enumerable.Empty <OperationResultMessage>();
        }
        public sealed class FailedOperationResult : OperationResult
        {
            private readonly List<OperationResultMessage> _messages;
            public FailedOperationResult(params OperationResultMessage[] errors)
            {
                _messages = new List<OperationResultMessage>(errors ?? Enumerable.Empty<OperationResultMessage>());
            }
            public override bool Succeeded => false;
            public override IEnumerable <OperationResultMessage> Messages
                => new ReadOnlyCollection <OperationResultMessage>(_messages);
        }
    }
}

在分析代码后,有两个密切相关的特殊性:

  • OperationResult类有一个私有构造函数。
  • SuccessfulOperationResultFailedOperationResult类都嵌套在OperationResult中并从中继承。

嵌套类是从OperationResult类继承的唯一方法,因为作为类的成员,嵌套类可以访问其私有成员,包括构造函数。否则,无法从OperationResult继承。

从本书开始,我已经多次重复了灵活性;但你并不总是想要灵活性。有时,您希望控制您公开的内容以及您允许消费者做的事情。

在这种特定的情况下,我们可以使用受保护的构造函数,或者我们可以实现一种更奇特的方法来实例化成功和失败实例。然而,我决定利用这个机会向您展示如何在适当的位置锁定实现,从而使它不可能通过从外部继承进行扩展。我们本可以在类中构建机制以允许受控的可扩展性,但对于这一个,让我们将其严格锁定!

从这里开始,唯一缺少的部分就是操作本身和使用该操作的客户端。让我们先看看操作:

namespace OperationResult.StaticFactoryMethod
{
    public class Executor
    {
        public OperationResult Operation()
        {
            // Randomize the success indicator
            // This should be real logic
            var randomNumber = new Random().Next(100);
            var success = randomNumber % 2 == 0;
            // Return the operation result
            if (success)
            {
                return OperationResult.Success(randomNumber);
            }
            else
            {
                var error = new OperationResultMessage(
                    $"Something went wrong with the number '{randomNumber}'.",
                    OperationResultSeverity.Error
                );
                return OperationResult.Failure(error);
            }
        }
    }
}

前面代码块中突出显示的两行显示了这一新改进的优雅。我发现这段代码很容易阅读,这是我的目标。我们现在有两种方法可以清楚地定义我们在使用它们时的意图:SuccessFailure

消费者使用的代码与我们之前在其他示例中看到的代码相同,因此我将在这里省略它。

利与弊

下面是操作结果设计模式的一些优点和缺点。

优势

它比抛出一个Exception更显式,因为操作结果类型被显式指定为方法的返回类型。这比知道操作及其依赖项可以引发什么类型的异常更为明显。

另一个优点是执行速度快;返回对象比引发异常更快。没那么快,但还是快了。

缺点

使用操作结果比抛出异常更复杂,因为我们必须手动将其传播到调用堆栈(也称为被调用方返回并由调用方处理)。如果操作结果必须上升到多个级别,则尤其如此,这可能是不使用模式的指示。

很容易公开并非在所有场景中都使用的成员,创建一个大于需要的 API 表面,其中某些部分仅在某些情况下使用。但是,在这和花费无数时间设计完美系统之间,有时暴露int? Value { get; }财产可能是一个更可行的选择。从那里,您可以选择将曲面缩小到最小。运用你的想象力和设计技巧来克服这些挑战!

总结

在本章中,我们访问了操作结果模式的多种形式,从增广布尔型到包含消息、值和成功指标的复杂数据结构。我们还探索了静态工厂和私有构造函数来控制外部访问。此外,在所有这些探索之后,我们可以得出结论,围绕操作结果模式几乎有无限的可能性。每个特定的用例都应该说明如何实现它。从这里开始,我相信您有足够的关于模式的信息,可以自己探索更多的可能性,我强烈鼓励您这样做。

此时,我们通常会探索操作结果模式如何帮助我们遵循坚实的原则。但是,它太依赖于实现,因此这里有几点:

  • OperationResult类封装结果,从其他系统的组件(SRP)中提取责任。
  • 在多个示例中,我们使用Value属性违反了 ISP。这是次要的,可以用不同的方法来完成,这可能会导致更复杂的设计。
  • 我们可以将操作结果视图模型DTO进行比较,但通过操作(方法)返回。从那个里,我们可以添加一个抽象,或者继续返回一个具体的类,我们可以认为这违反了 DIP。
  • 当优势超过了这两种违规行为的轻微和有限影响时,我不介意让它们溜走(原则是理想、规则,而不是法律)。

本章总结了组件级部分的设计,并引出了应用级部分的设计,我们将在其中探索更高层次的设计模式。

问题

让我们来看看几个练习题:

  1. 在执行异步调用(如 HTTP 请求)时返回操作结果是一个好主意吗?
  2. 我们使用静态方法实现的模式的名称是什么?
  3. 返回操作结果是否比引发异常更快?

进一步阅读

以下是我们在本章中学习的一些链接:

  • 我博客上的一篇关于异常的文章(标题:异常入门指南基础知识https://net5.link/PpEm
  • 我博客上的一篇关于操作结果的文章(标题:操作结果设计模式https://net5.link/4o2q
  • 我有一个操作结果模式的通用开源实现,允许您通过向项目中添加 NuGet 包来使用它:https://net5.link/FeGZ