继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

在TypeScript中实现中介者模式:全面指南

jeck猫
关注TA
已关注
手记 460
粉丝 75
获赞 402
简介

中介者模式。参考自:www.geeksforgeeks.org

随着软件项目的规模扩大,管理各个组件之间的交互变得极具挑战性。当应用程序内的不同模块直接相互沟通时,系统很快就会变得错综复杂,这使得维护、测试和扩展变得更加困难。这时,中间人模式就派上用场了。

中介者模式是一种行为型设计模式,它封装了对象之间的交互方式。对象不是直接进行通信,而是通过中介对象来通信。中介者处理不同组件之间动作协调的逻辑,从而减少它们之间的依赖。

本质上,中介者充当中间人角色,集中对象之间的通信。这对于大型复杂系统尤其有用,在这些系统中,保持清晰和可管理的结构至关重要。

例子

想象一个复杂的UI应用,其中不同的组件,如按钮、文本框和下拉菜单需要互相作用。例如,从下拉菜单中选择一个项可能会启用或禁用某些按钮,同时修改文本框的内容。如果每个组件都负责与其他组件的互动,代码可能会因此变得非常复杂,每个组件都需要知道它所交互的每个其他组件的状态和行为。中介者模式通过引入一个中介来处理这些互动,使每个组件只需与中介交互,而不需要直接和其他组件交互。

现代软件开发中的中介模式

中介模式在UI开发界特别受欢迎,但其用途远不止于此。它在任何需要组件解耦的架构中都是不可或缺的,有助于构建更加容易维护和扩展的系统。MediatR库是一个著名的实现,通过提供一个简单的API来发送组件之间的消息,处理诸如日志记录、验证和授权等横切关注点,并促进命令和查询责任隔离(CQRS)模式。

不过,中介者模式和 MediatR 实现的好处不仅限于 .NET,随着 TypeScript 在前后端开发中的流行,也可以利用这些原则来构建干净、易于维护的代码库。

聊聊中介模式的利弊

在开始用 TypeScript 实现之前,我们首先应该了解中介者模式的优点和可能的缺点。

好的方面:
  1. 组件解耦:

中介模式减少组件之间的直接依赖。每个组件只需知道中介,而无需了解其他组件。这样就减少了组件间的直接依赖,使得修改或替换单个组件更容易,而不会影响到整个系统。

2. 集中式控制:

通过集中通信,中间人可以确保一致的交互模式,管理复杂的工作流程,并处理如日志记录、验证和事务管理等跨领域问题。

3. 提升可维护性:

有了中介管理交互,代码库变得更模块化。这种模块化使得维护变得更简单,开发人员可以专注于单个组件或中介逻辑,而无需关注所有组件之间的细节交互。

4. 更易于测试的

因为组件通过与中介交互而不是直接相互交互,所以在测试时更容易将组件隔离。你可以单独测试中介逻辑,而不必同时测试组件,确保各自都能独立运行,这样每个单独的部分都能正确运行。

5. 简化复杂的互动:

调解器可以应对多个组件之间的复杂交互情况,使原本复杂且容易出错的直接组件交互简化,更符合中文表达习惯。

是缺点:

1. 成为单一中介的可能性:

如果没有妥善管理,调解者可能会变成一个“无所不知的大对象”,知道太多并且过度介入。这可能导致单一故障点,从而使调解者难以维护。

2. 费用:

引入一个中介者会增加一层抽象层次,可能会带来一些额外的负担。对于极其简单的互动来说,这种模式可能就不太合适了。

3. 透明度下降:

将控制权集中到中介对象中有时会使得追踪组件间的交互过程变得更困难,特别是在复杂的系统中。开发人员可能发现跟踪数据和控制在组件间的流动较为困难。

4. 实施的复杂性

引入中介者会增加初始开发过程的复杂度,尤其是在较大系统之中,中介者需要处理多种不同类型的交互。

在什么情况下使用中介模式

在以下这些应用场景中,中介者模式最为有益。

  • 多个组件需要以复杂的方式相互作用。 例如,在一个包含多个字段的表单中,一个字段的变化应触发其他字段的更新。
  • 解耦很重要。 如果预计这些组件会频繁更改或希望确保这些组件可以在不同的上下文中重用,中介者模式可以非常有效。
  • 横切关注点需要集中处理。 如果需要一致处理这些组件的日志记录、验证或授权,中介者可以作为这些关注点的集中处理点。
我们将要讨论的内容

在这篇文章中,我们将讨论如何在TypeScript中实现这种Mediator模式。这是在.NET中广为使用的一种Mediator模式的实现。

  1. 项目搭建: 我们从新建一个TypeScript项目并安装必要的依赖项开始。
  2. 创建中介者: 我们将定义核心的中介者逻辑,负责将请求分发到相应的处理器。
  3. 定义请求和处理器: 我们将创建一些示例请求和处理器,以展示如何使用中介者管理应用程序内部的交互。
  4. 实现依赖注入: 我们将使用tsyringe库来管理依赖项,并确保我们的处理器能正确地被注入到中介者中。
  5. 整合所有内容: 最后,我们将所有内容整合成一个简单的示例,展示TypeScript中中介者模式的强大和灵活性。

在中介模式中,你通常会打交道的有三个主要组件。

  • 请求: 这些代表一个操作或查询,例如执行某个操作的命令或获取数据的查询。
  • 处理器: 处理器负责处理特定的请求。每个请求有一个处理器,它知道如何处理。
  • 中介: 中介充当中间人,将请求路由到适当的处理器。

到本文结束时,你将学会在TypeScript中实现和使用中介者模式的方法,从而能够写出更干净、更易维护、更易扩展的代码。

为为什么要在TypeScript中使用中介模式?

尽管中介者模式起源于 .NET 世界,但其原则是通用的,并且在任何需要解耦和整洁架构的环境中都非常有用。这里的“非常有用”可以改为“特别有用”,使语气更加一致。在 TypeScript 中,这种模式在后端和前端应用中都特别有用。

  • 后端(Node.js): 它可以帮助整理业务逻辑和数据访问部分,让它们更容易管理和测试。
  • 前端(React/Angular): 它可以将界面组件与业务逻辑分离,提高组件的复用性与测试性。
开始你的项目

要在 TypeScript 中实现中介者模式,我们需要建立一个包含几个关键依赖项的项目。以下是如何开始。

步骤 1:创建一个新的 TypeScript 项目

首先,让我们为我们的项目创建一个新的文件夹,并使用npm进行初始化。

    mkdir 中介者模式-ts/  
    cd 中介者模式-ts/  
    npm init -y  # 初始化 npm 项目

然后安装并初始化 TypeScript。

首先,你需要通过npm安装typescript,命令是npm install typescript --save-dev。然后,你需要初始化typescript,可以通过运行npx tsc --init来完成。

这个命令会生成一个 tsconfig.json 文件。你可以根据需要调整设置,但默认配置通常已经足够满足大多数项目的需求了。

步骤 2:安装必要的依赖项

我们需要两个额外的包来在 TypeScript 中模拟中介者模式。

  • reflect-metadata:用于处理元数据,对于依赖注入来说非常重要。
  • tsyringe:一个简单且轻量的依赖注入库。

使用npm进行安装。

npm install reflect-metadata tsyringe  # 安装反射元数据和tsyringe插件

最后,请确保在应用程序启动的地方导入reflect-metadata(通常是index.tsapp.ts):

以下是如何导入reflect-metadata的示例:

import 'reflect-metadata';

以确保其能够正常工作。

    import 'reflect-metadata';
创建中介模式

调解器是中介模式的核心。它负责接收请求并将它们分发给合适的处理单元。

第一步:定义请求接口和处理程序接口

首先,我们将为 RequestHandlerMediator 定义接口。这些接口将确保各种请求和处理器具有一致性。

// 定义请求接口
export interface IRequest<TResponse> {}

// 定义请求处理器接口,处理特定类型的请求并返回响应
export interface IRequestHandler<TRequest extends IRequest<TResponse>, TResponse> {
  handle(request: TRequest): Promise<TResponse>;
}

// 定义中介接口,用于发送请求并返回响应
export interface IMediator {
  send<TRequest extends IRequest<TResponse>, TResponse>(
    request: TRequest
  ): Promise<TResponse>;
}
  • IRequest: IRequest:一个通用接口,所有请求都将实现这个接口。类型参数TResponse表示请求期望的响应类型。
  • IRequestHandler: IRequestHandler:一个用于处理器的泛型接口。它接收一个请求并返回一个响应。
  • IMediator: IMediator:中介者接口,用于发送请求并接收响应。
第2步:实现调解者

接下来,我们将实现Mediator类,这个类将负责为给定请求找到合适的处理器并执行该请求的处理。

    import { injectable, container } from "tsyringe";  

    @injectable()  // 注入依赖
    export class Mediator implements IMediator {  // 实现IMediator接口
      async send<TRequest extends IRequest<TResponse>, TResponse>(  
        request: TRequest  
      ): Promise<TResponse> {  
        const handler = container.resolve<IRequestHandler<TRequest, TResponse>>(  // 获取处理器实例
          (request as any).constructor.name + "Handler"  // 构造处理器名称
        );  
        return handler.handle(request);  // 处理请求
      }  
    }
  • 发送方法: send 方法从依赖注入容器中取出合适的处理器,接着调用它的 handle 方法来处理请求。
定义请求及处理器

我们可以定义一些具体的请求及其相应的处理器。

第一步:发起一个请求

让我们先定义一个简单的命令,用来创建一个用户。这个命令会包含用户的名字和邮箱。

    // 创建用户命令类,实现IRequest接口,用于创建用户时传递用户名和邮箱
    // CreateUserCommand.ts
    export class CreateUserCommand implements IRequest<string> {
      constructor(public name: string, public email: string) {}
    }
步骤 2:创建一个处理程序

接下来,我们就来实现一下处理用户创建命令的处理器。这个处理程序主要负责处理用户创建命令的业务逻辑。

    // CreateUserHandler.ts
    import { injectable } from "tsyringe";
    import { IRequestHandler } from "./Mediator";
    import { CreateUserCommand } from "./CreateUserCommand";

    @injectable()
    export class CreateUserHandler implements IRequestHandler<CreateUserCommand, string> {
      async handle(request: CreateUserCommand): Promise<string> {
        // 创建用户的业务逻辑
        const userId = `user-${Math.random().toString(36).substring(7)}`;
        // 通常这里会将用户信息保存到数据库中。
        console.log(`用户创建成功,ID为:${userId}`);
        return userId;
      }
    }
  • CreateUserHandler: 此处理程序处理 创建用户命令 CreateUserCommand 并返回一个表示用户 ID 的字符串。通常这里会与数据库交互来保存用户数据。
实现依赖注入功能

为了有效地管理依赖,我们将使用 tsyringe 库来实现依赖注入。这使我们能够自动将正确的处理器注入到中介者中。

第一步:注册依赖

首先:注册依赖

我们需要将处理程序注册一下到依赖注入容器里。这告诉 tsyringe 程序在需要处理程序的时候要实例化哪个类。

    import { container } from "tsyringe";
    import { CreateUserHandler } from "./CreateUserHandler";
    import { Mediator } from "./Mediator";

    // 注册处理程序
    container.register("CreateUserCommandHandler", { useClass: CreateUserHandler }); // 注册创建用户命令处理器

    // 注册中介
    container.registerSingleton<IMediator, Mediator>("Mediator"); // 注册单例中介对象
  • container.register:CreateUserHandler 注册到容器。
  • container.registerSingleton:Mediator 注册为单例模式,确保在整个应用程序中使用同一个实例,而不是每次都创建新的实例。
把一切都放在一起

现在我们已经把所有部分都准备好了,来看看中介者模式怎么发挥作用。

使用示例

我们来举一个简单的例子,通过中介发送一个 CreateUserCommand 命令并接收响应消息。

    import "reflect-metadata";  
    import { container } from "tsyringe";  
    import { Mediator } from "./Mediator";  
    import { CreateUserCommand } from "./CreateUserCommand";  
    import { CreateUserHandler } from "./CreateUserHandler";  

    // 注册处理器和中介者  
    container.register("CreateUserCommandHandler", { useClass: CreateUserHandler });  
    container.registerSingleton<IMediator, Mediator>("Mediator");  

    // 示例用法  
    async function main() {  
      const mediator = container.resolve<IMediator>("Mediator");  
      const command = new CreateUserCommand("John Doe", "john@example.com");  
      const userId = await mediator.send(command); // 获取用户ID  
      console.log(`新的用户ID是: ${userId}`);  
    }  

    main();

要运行这个应用程序,先确保 TypeScript 已经编译好,然后运行生成的 JavaScript 文件。

运行以下命令编译和运行程序:

运行以下命令编译程序:

npx tsc

运行以下命令执行编译后的程序:

node dist/index.js


如果所有设置都正确,你应该能看到如下结果:
用户创建成功,ID是:user-abc123  
用户ID是:user-abc123


这体现了中介者模式如何有效地集中通信交互,从而使得代码更整洁、更容易维护。

# 结尾:

中介模式是一种强大的工具,用于管理复杂应用程序中组件间的通信。通过将请求与处理这些请求的程序分开,并通过中介管理交互,你能够创建一个更模块化和易于维护的架构。

在这份指南里,我们介绍了如何在 TypeScript 中实现中介者模式,从项目设置、接口定义到实现一个实际工作的示例。虽然这种实现比较基础,但它提供了一个坚实的基础,你可以在此基础上构建更复杂的系统,不论是后端使用 Node.js,还是前端使用 Angular 或 React 等框架。

在你的 TypeScript 项目中使用中介者模式,可以保持代码库干净、有序且易于扩展——以便随着你的应用程序的成长而轻松扩展。
打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP