中介模式定義了一個單獨的(中介)對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。
在實際的項目中,程序由許多對象組成,對象間的交流錯綜複雜。
隨着應用程序的規模增大,對象越來愈多,他們之間的關係也越來複雜。對象間很容易出現相互引用而導致程序無法運行。同時開發者需要改變或者刪除某一個對象時候,需要查找並且改造所有引用到它的對象。這樣一來,改造的成本會變的非常高。
但中介者模式可以讓各個對象之間得以解耦。
之前的場景下,如果 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 在基礎類中,具體的業務類分佈在各個業務代碼中。
這裏沒有使用任何依賴注入的框架,原因是因為如果引入了依賴注入會大大增加複雜度。同時前端的業務都是分散的,沒有必要都放在一起。在組件需要使用到服務候才將其裝載起來。
鼓勵一下
如果你覺得這篇文章不錯,希望可以給與我一些鼓勵,在我的 github 博客下幫忙 star 一下。
博客地址