本文详解如何在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中介模式全局控制器的资料请关注本站其它相关文章!