JS利用中介模式开发全局控制器

作者:异步漫步时间:2026-10-31 06:05:54浏览:545

本文详解如何在JavaScript项目中应用中介者模式构建全局控制器,通过封装对象交互、解耦业务逻辑,解决复杂项目中的引用混乱问题。文章提供了从基础控制器到业务注入的完整代码示例,探讨如何避免“上帝类”陷阱并实现按需加载的服务管理。

中介模式

中介者模式通过引入一个独立的中介对象,来封装一组对象之间的交互行为。将原本分散在各对象间的交互逻辑统一委派给该中介对象处理,从而避免对象之间产生直接引用和耦合。

在实际的项目开发中,系统通常由大量对象构成,这些对象之间的交互关系往往错综复杂。

中介模式

随着应用程序规模的不断扩大,对象数量激增,相互之间的关系网也日益复杂。对象间若存在过多的直接引用,极易导致程序运行异常或维护困难。同时,当开发者需要修改或删除某个特定对象时,必须逐一查找并改造所有引用该对象的关联模块,这使得改造成本变得极高。

引入中介者模式能够有效实现各个对象之间的解耦。

中介模式

在之前的场景下,如果对象 A 发生变更,开发者需要手动修改 B、D、E、F 共 4 个相关对象;而采用中介者模式后,只需调整中介者这一个对象即可,大幅降低了维护工作量。

中介者模式的主要优势在于简化了对象间的交互逻辑,但其潜在弊端是中介类可能演变为庞大且复杂的“上帝类”(God Class)。因此,在使用该模式时,设计阶段必须保持克制,严格控制中介类的职责范围。

实际使用

在前端项目开发过程中,存在许多与具体业务无关的通用功能,但这些功能往往散落在各个业务模块中,难以统一管理。我们可以借鉴中介者模式的思想,构建一个统一的全局控制器来解决这一问题。

基础控制器

// axios 请求工具
import axios from "axios";
// mitt 微型发布订阅工具
import mitt from "mitt";
const createApi = ($controller) => {
  const api = axios.create({
    baseURL: "/api",
    timeout: 10000,
  });
  api.interceptors.request.use(() => {
    // 可以通过 $controller.tmpCache 缓存一些数据
  });
  return api;
};
const createBus = () => mitt();
class Controller {
  // 临时缓存,也可以添加更复杂的缓存
  tmpCache: Record<string, any> = {};
  // 事件总线
  bus = createBus();
  constructor() {
    this.api = createApi(this);
  }
  static instance: Controller | null = null;
  static getInstance() {
    if (!Controller.instance) {
      Controller.instance = new Controller();
    }
    return Controller.instance;
  }
}
export default Controller;

此时控制器中包含一个极简的缓存机制 tmpCache、发布订阅工具以及服务端请求方法 api。开发者只需导入 Controller 类即可直接使用。如果后续需要更复杂的缓存策略或改造 api(例如切换为 fetch)请求方法,开发者可以迅速进行替换,而无需修改对应的业务文件。

import axios from "redaxios";
const createApi = ($controller) => {
  const api = axios.create({
    baseURL: "/api",
  });
  const getOld = api.get;
  api.get = (...params) => {
    // 如果出发缓存直接返回
    // if (xxx) {
    //   return $controller.tmpCache
    // }
    return getOld(...params);
  };
  return api;
};

添加用户类

登录的用户信息及其相关操作是对象交互的核心部分,将其纳入控制器管理之中。

class User {
  readonly $controller: Controller;
  user: User | null = null;
  constructor($controller: Controller) {
    this.$controller = $controller;
  }
  getData(key?: string) {
    return key ? this.user[key] : this.user;
  }
  // 登录
  login(params) {
    $controller.api.post("/login", params).then((response) => {
      // 处理授权以及 user 信息
    });
  }
  // 退出
  logout() {
    $controller.api.post("/logout").then(() => {
      // 清理对应数据
      $controller.tmpCache = {};
      this.user = null;
    });
  }
  // 设置用户配置
  setSetting(params) {
    return $controller.api.post("/setting", params).then(() => {
      this.user.setting = params;
    });
  }
}
class Controller {
  constructor() {
    this.api = createApi(this);
    this.user = new User(this);
  }
  logout() {
    this.user.logout();
  }
}

此时控制器已具备前端开发中“大部分”常用功能,如数据缓存、数据请求、登录用户信息处理等。在大多数情况下,该中介类已足够使用。至于其他工具类,除非有 80% 的业务场景都会用到,否则不建议直接添加此类中。但对于这些工具进行一层简单的封装也是必要的(需考虑后续维护成本)。

添加业务类

此时控制器已包含用户信息、多种缓存策略、请求方法等核心能力。我们可以通过依赖注入的方式来实现其他业务逻辑的接入。

class BasicService {
  constructor($controller) {
    this.api = $controller.api;
    this.bus = $controller.bus;
  }
}
class OrderService extends BasicService {
  static xxxKey = "xxxx";
  constructor($controller) {
    super($controller);
    // 使用全局缓存
    this.cache = $controller.xxxCache;
    // 或者构建一个新的 cache 挂载到 controller 上
    this.cache = $controller.getCreatedCache("order", XXXCache);
  }
  getOrders = async () => {
    if (this.cache.has(OrderService.xxxKey)) {
      return this.cache.get(OrderService.xxxKey);
    }
    let orders = await this.api.get("xxxxx");
    // 业务处理,包括其他的业务开发
    order = order.map();
    this.cache.set(OrderService.xxxKey, orders);
    // 可以发送全局事件
    this.bus.emit("getOrdersSuccess", orders);
    return orders;
  };
  clear() {
    // 如果构建一个新的缓存对象,直接 clear 即可 this.cache.clear();
    this.cache.delete(OrderService.xxxKey);
  }
}
class Controller {
  // 临时缓存,也可以添加更复杂的缓存
  tmpCache: Record<string, any> = {};
  // 事件总线
  bus = createBus();
  services: Record<string, BasicService> = {};
  getService(serviceCls) {
    // 同一个类 name 为相同的名称
    const { name } = serviceCls;
    if (!this.services[name]) {
      this.services[name] = new serviceCls(Controller.instance);
    }
    return this.services[name];
  }
}

开发者可以在具体的代码中这样使用。

import Controller from "../../controller";
import OrderService from "./order-service";
// A 页面
const orderService = Controller.getInstance().getService(OrderService);
const priceService = Controller.getInstance().getService(PriceService);
// B 页面都会使用同一个服务
const orderService = Controller.getInstance().getService(OrderService);

有同学可能会疑惑,为什么 Controller 不能直接导入或自动注入 OrderService。采用当前使用方式的原因是,Controller 和 BasicService 通常位于基础类库中,而具体的业务类分散在各个业务代码模块中。

这里没有使用任何现成的依赖注入框架,原因是引入框架会大幅增加系统复杂度。同时,前端的业务逻辑通常是分散的,没有必要将所有服务集中放置。只有在组件真正需要使用到服务时,才将其装载起来,从而实现按需加载。

以上就是JS利用中介模式开发全局控制器的详细内容,更多关于JS中介模式全局控制器的资料请关注本站其它相关文章!

作者最新文章