批处理命令 替换_如果使用其他命令和处理程序替换

批处理命令 替换

重点(Top highlight)

更好的软件设计(BETTER SOFTWARE DESIGN)

If you want to learn how I use commands and handlers to keep my code neat and tidy, read on. It’s not about avoiding if-elseif-else. It’s about finding a more suitable approach.

如果您想学习如何使用命令和处理程序来保持代码整洁,请继续阅读。 这与避免if-elseif-else 。 这是关于找到一种更合适的方法。

Bear in mind that no single approach will ever get rid of traditional branch-based programming. You need to build a repository of techniques to draw from.

请记住,没有任何一种方法可以摆脱传统的基于分支的编程。 您需要建立一个技术库以供借鉴。

The method we’ll walk thru now is just one of many.

我们现在要走的方法只是众多方法之一。

if-else is, at its very core, not bad. It’s merely just a hammer and nail situation we got going. In programming 101, you’ll learn conditional statements, and lots of developers never mature their practices beyond that.

if-elseif-else还不错。 这仅仅是我们所要解决的问题。 在编程101中,您将学习条件语句,并且许多开发人员从没有超出其成熟度。

But, if-else and switch are often not ideal. Better approaches, such as polymorphic execution and dictionaries, are typically neglected.

但是, if-elseswitch通常并不理想。 通常忽略更好的方法,例如多态执行和字典。

我们要避免传统的有条件分支。 (We want to avoid traditional, conditional branches.)

I’ve written an article proposing a way of replacing conditional branching with polymorphic execution. To get some context, I’ll briefly repeat some of the earlier article examples before we deep dive into commands and handlers.

我写了一篇文章,提出了一种用多态执行替换条件分支的方法。 为了获得一些背景信息,在深入研究命令和处理程序之前,我将简要重复一些前面的示例。

Here’s the example of what we’d like to avoid: Nasty, difficult to extend, branching on a discrete value.

这是我们要避免的示例:讨厌,难以扩展,分支离散值。

Image for post
Complicated, headache-inducing branching
复杂的头痛诱发分支

Besides the freakish use of if-elseif-else, the main issue is you need to add a branch for every new update reason. A clear violation of the Open/Closed and Single Responsibility principles.

除了if-elseif-else的怪异用法外,主要问题是您需要为每个新的更新原因添加一个分支。 明显违反开放/封闭和单一责任原则。

Each branch can basically be converted to its own command and corresponding handlers.

每个分支基本上都可以转换为自己的命令和相应的处理程序。

Let’s take a look at how that’s possible.

让我们看看这是怎么可能的。

使用命令和处理程序来简化您的应用程序。 (Using commands and handlers to simplify your application.)

📝 GitHub Repo

📝GitHub回购

I won’t preach the theory about what commands, queries, and handlers are. There are plenty of resources on this topic. Instead, I’ve composed a brief list of what the advantages may be.

我不会讲关于什么是命令,查询和处理程序的理论。 有关此主题,有很多资源。 相反,我简要列出了可能的优点。

  1. Testing becomes tremendously easier.

    测试变得非常容易。

    You don’t need to update existing tests to account for new features. If a command requires additional processing, you create another handler, which you test in isolation.

    您无需更新现有测试即可说明新功能。 如果命令需要其他处理,则可以创建另一个处理程序,并对其进行单独测试。

  2. Multiple handlers can handle one command.

    多个处理程序可以处理一个命令。

    As you’ve likely already noted, dispatching one command may invoke one or more handlers. In this way, you can add new functionality without touching existing code.

    您可能已经注意到,调度一个命令可能会调用一个或多个处理程序。 这样,您可以添加新功能而无需触及现有代码。

  3. Stupid, Simple Classes.

    愚蠢的简单类。

    A command is a bag of properties with no setters. Not much can go wrong here. Likewise, for a handler, it’s a class with only one public method.

    命令是没有设置器的属性包。 这里没什么大不了的。 同样,对于处理程序,这是只有一个公共方法的类。

  4. Controller actions adhere to a Request-Delegate-Response pattern.
    They’ll contain no business or persistence logic whatsoever.

    控制器动作遵循Request-Delegate-Response模式。
    它们将不包含任何业务或持久性逻辑。

If you’re practicing event-storming, I’m sure you’re already completely aligned with why commands and handlers are awesome.

如果您正在练习event-storming ,那么我相信您已经完全理解了为什么命令和处理程序很棒。

I ended the previously mentioned article, hinting how you can use dynamic command dispatching to eliminate unnecessary branching. And now, you’ll see one way of implementing commands and handlers.

我结束了前面提到的文章,暗示了如何使用动态命令分派来消除不必要的分支。 现在,您将看到一种实现命令和处理程序的方法。

最后,一些代码! (Finally, some code!)

To follow this code, let me very quickly summarize to you what we want to achieve, in a general sense.

要遵循此代码,让我从一般意义上非常快速地向您总结我们想要实现的目标。

We want to say, “Okay, something needs to happen. Here are the values. I don’t care who handles it, just let me know when it’s done”.

我们想说:“好的,有些事情需要发生。 这些是值。 我不在乎谁来处理它,只要完成时告诉我即可。”

There are three acceptance criteria we’ll need to fulfill:

我们需要满足三个接受标准:

  1. A command can be dispatched without the caller knowing the concrete handlers.

    可以在调用者不知道具体处理程序的情况下分派命令。
  2. Every handler matching the command needs to be executed.

    每个与命令匹配的处理程序都需要执行。
  3. New commands or processing steps do not require you to modify existing code.

    新命令或处理步骤不需要您修改现有代码。

我们将从最外层开始,逐步进行。 (We’ll start from the outermost layer and work our way in.)

Departing from the controller’s perspective, it’s irrelevant to know about concrete handlers or even interfaces. The action should only be focused on data.

从控制器的角度出发,了解具体的处理程序甚至接口无关。 该操作应仅针对数据。

For this, we want the controller action to be just as simple as this below.

为此,我们希望控制器动作像下面这样简单。

Update email endpoint
Update email endpoint
更新电子邮件端点

You should get the gist it, even though this is C# aspnetcore. Simply put, it’s a controller action — the endpoint and its implementation.

即使这是C#aspnetcore,也应掌握要点。 简而言之,这是一个控制器动作-端点及其实现。

I know what you’re thinking: “where’s the error handling?!”. Don’t worry. You’re right. It should be there. But for brevity, I’ve left that part out so we can focus on the concept of dispatching commands.

我知道您在想什么:“错误处理在哪里?!”。 不用担心你是对的。 它应该在那里。 为了简洁起见,我省略了这一部分,以便我们可以集中讨论调度命令的概念。

The controller has a dependency on a CommandDispatcher. We’ll get to that class later. The dispatcher class has a single method DispatchAsync(command). That’s all you’ll need to know for now.

控制器依赖于CommandDispatcher 。 我们稍后再上那堂课。 调度程序类具有单个方法DispatchAsync(command) 。 这就是您现在需要知道的全部。

This allows our controller to only care about validating the correctness of the data it receives and sending commands. How data is handled after dispatching is entirely irrelevant for the controller.

这使我们的控制器仅关心验证接收和发送的数据的正确性。 调度后如何处理数据与控制器完全无关。

Each “update reason” requires its own endpoint, with its own data shape — i.e., the command to send.

每个“更新原因”都需要有自己的终结点,并具有自己的数据形状(即要发送的命令)。

At this point, implementing new features such as “update username” is as simple as creating a new endpoint and send the command.

此时,实现新功能(例如“更新用户名”)就像创建新端点并发送命令一样简单。

Update username endpoint
Update username endpoint
更新用户名端点

When using this approach, creating endpoints becomes enormously trivial. And that’s a good thing.

使用这种方法时,创建端点变得非常琐碎。 那是一件好事。

Our endpoint is essentially done now.So, let’s move on.

现在我们的端点已经基本完成,让我们继续。

命令和处理程序是所有业务逻辑所在的位置。 (Commands and handlers are where all the business logic is situated.)

With commands, you essentially only want to care about two things: immutability and data correctness.

使用命令,您基本上只想关心两件事:不变性和数据正确性。

They are just plain, old regular classes. Nothing fancy at all. Take a look at this ChangeEmailCommand.

它们只是普通的老式常规课程。 一点都不花哨。 看看这个ChangeEmailCommand

Plain old command class
Plain old command class
普通的老命令类

Obviously, this command class doesn’t do much. That’s the whole point. Its purpose is to be passed on to a handler.

显然,这个命令类并不能做很多事情。 这就是重点。 其目的是传递给处理程序。

Which brings us to the handler. Take a few to read through the code below. I’ll try to describe what’s going on after.

这将我们带到处理程序。 花一些时间阅读下面的代码。 我将尝试描述发生了什么。

Simple, testable command handler.
Simple, testable command handler.
简单,可测试的命令处理程序。

First, we have an interface that all command handlers need to implement. The interface is important when you need dynamic type discovery. We’ll get to that in a minute.

首先,我们有一个所有命令处理程序都需要实现的接口。 当需要动态类型发现时,该界面很重要。 一分钟后,我们就会解决。

Second, I’ve created a simple handler that knows how to deal with ChangeEmailCommands. The generic parameter of ICommandHandlerAsync tells us, “this handler needs to be invoked whenever a ‘change email command’ is dispatched.”

其次,我创建了一个简单的处理程序,该处理程序知道如何处理ChangeEmailCommand 。 ICommandHandlerAsync的通用参数告诉我们:“每当发送“更改电子邮件命令”时,都需要调用此处理程序。”

Do you get a feeling of how d*mn testable this class is? That’s the whole point. It should be stupidly easy to test. The class is very focused. One method, one dependency.

您是否对此类有d * mn可测性感到? 这就是重点。 它应该很容易测试。 该课程非常集中。 一种方法,一种依赖性。

If you’re used to “Service” classes, you know how crazy constructors sometimes get. This approach completely eliminates constructor bloat.

如果您习惯了“服务”类,那么您就会知道有时构造函数会变得多么疯狂。 这种方法完全消除了构造函数的膨胀。

调度程序本身。 非常简单和强大。 (The dispatcher itself. Incredibly simple and robust.)

You’ve already seen the dispatcher interface. Clean and simple. But let’s jog your memory once more.

您已经看过调度程序界面。 干净简单。 但是,让我们再次慢跑您的记忆。

Command dispatcher’s public interface
Command dispatcher’s public interface
指挥调度员的公共界面

Before getting distracted with the implementation, let me just reiterate what we need to achieve with the CommandDispatcher.

在开始关注实现之前,让我重申一下我们需要使用CommandDispatcher实现的CommandDispatcher

We want to say, “here’s a command, go grab all the matching handlers and pass the command to each handler.”

我们想说,“这是一个命令,去获取所有匹配的处理程序,并将命令传递给每个处理程序。”

This means, for each command class, we need a list of matching command handlers. In code, we can express this intent with a dictionary, where the key is the type of command, and the value is a list of handlers.

这意味着,对于每个命令类,我们需要一个匹配的命令处理程序列表。 在代码中,我们可以用字典来表达这种意图,其中的键是命令的类型,值是处理程序的列表。

Take whatever time you need to read thru this. OOP people might find this super easy, while others won’t. I’ll describe what we got going, below the code.

花点时间阅读一下。 OOP的人们可能会觉得这超级简单,而其他人则不会。 我将在代码下方描述我们的工作。

Command dispatcher with dictionary lookup
Command dispatcher with dictionary lookup
具有字典查找功能的命令调度程序

Not anything too fancy. Just a bit of reflection. This might put off some non-OOP people. Don’t worry, it’s likely you can easily find some way of achieving the same in a functional language.

没有什么太花哨的。 只是一点点反思。 这可能会推迟一些非OOP人员。 不用担心,很可能您可以轻松找到以功能语言实现相同目标的某种方法。

It’s irrelevant to describe the actual code. It’s just plain old C#. What’s important is the intent.

描述实际的代码无关紧要。 这只是普通的旧C#。 重要的是意图。

The most important part is to have a mechanism for relating commands with handlers. For this, I’m using a dictionary.

最重要的部分是拥有一种将命令与处理程序相关联的机制。 为此,我正在使用字典。

Each key is a command type. The corresponding value is a list of handlers implementing ICommandHandlerAsync<commandType>.

每个键都是命令类型。 相应的值是实现ICommandHandlerAsync<commandType>的处理程序的列表。

When you invoke e.g.DispatchAsync(ChangeEmailCommand), the dispatcher tries to find the key of “Type: CommandHandler” inside the dictionary and return the list of registered handlers.

当您调用DispatchAsync(ChangeEmailCommand) ,调度程序将尝试在字典中查找“ Type:CommandHandler”的键,并返回已注册处理程序的列表。

Each handler is then invoked.

然后调用每个处理程序。

And that’s it. Pretty simple stuff.

就是这样。 很简单的东西。

通过动态类型发现将其连接起来。 (Wiring it all up with dynamic type discovery.)

Nothing is actually working at this point.

在这一点上实际上什么也没有。

You’ll need to provide the Dictionary to the command dispatcher somewhere.

您需要在某个地方将字典提供给命令调度程序。

Ideally, you create the command dispatcher and its dictionary somewhere at the application startup and register it with your dependency injection framework.

理想情况下,您可以在应用程序启动时的某个位置创建命令分派器及其字典,然后在依赖项注入框架中注册它。

Do you remember our 3rd acceptance criteria? That, new features/requirements should not require us to modify existing code?

您还记得我们的第三个验收标准吗? 那新功能/要求不应该要求我们修改现有代码吗?

If you register new commands and handlers with the dictionary by hand, you’re effectively modifying the existing code. It might be completely fine. Most often it is.

如果您手动在字典中注册新的命令和处理程序,则实际上是在修改现有代码。 可能完全没问题。 通常是这样。

But, if you want to push yourself and improve, then you should try to give dynamic type discovery a spin.

但是,如果您想推动自己并进行改进,那么应该尝试让动态类型发现变得轻松。

Again, we got a bunch of C# code that is completely irrelevant to describe but is perhaps interesting to some.

同样,我们得到了一堆与描述完全无关的C#代码,但也许对某些人来说很有趣。

Just know this code will go find all command handlers, register them with the dependency container, build a dictionary, and pass that dictionary to a new instance of the command dispatcher every time a CommandDispatcher is required by e.g. a controller.

只是知道此代码将查找所有命令处理程序,将它们注册到依赖项容器中,构建字典,然后在每次控制器(例如控制器)需要CommandDispatcher时将该字典传递给命令分配器的新实例。

Dynamic type discovery at the application startup
Dynamic type discovery at the application startup
在应用程序启动时进行动态类型发现

With this, there’s no need to touch your existing code. Not even when you create a new command or handler. Everything is wired up at the application start.

这样,您就无需触摸现有代码。 即使创建新命令或处理程序也不行。 在应用程序启动时,所有内容都已连接好。

Sure, it looks messy and your initial thought might be it’ll become a maintenance nightmare.

当然,它看起来很乱,您最初的想法可能是它将成为维护的噩梦。

I’ve written the code like this on purpose. Naturally, you’d want to do some method extraction refactoring to make it less obnoxious. For demonstration purposes, it’s completely fine.

我故意写了这样的代码。 自然地,您希望进行一些方法提取重构,以减少麻烦。 出于演示目的,这完全没问题。

The point is, you write this once, and all your plumbing is done. Write some unit tests and you’ll be all good.

关键是,您只需编写一次,所有管道便已完成。 编写一些单元测试,一切都会好起来的。

Resources for the curious
-------------------------GitHub repository by the author

升级编码 (Level Up Coding)

Thanks for being a part of our community! Subscribe to our YouTube channel or join the Skilled.dev coding interview course.

感谢您加入我们的社区! 订阅我们的YouTube频道或参加Skilled.dev编码面试课程

翻译自: https://levelup.gitconnected.com/replacing-if-else-with-commands-and-handlers-527e0abe2147

批处理命令 替换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值