结构型模式深度解析
结构型模式三兄弟:适配器、代理、装饰器——意图差异、前端落点(HOC、Proxy、API 兼容层)。
一句话概括
结构型模式解决的是”类和对象怎么组合成更大的结构“。前端最常用的三个是适配器模式(把不兼容的接口翻译成统一的,如兼容旧 API)、代理模式(控制对目标的访问,如 ES6 Proxy、图片懒加载)、装饰器模式(动态给对象加功能,如高阶组件 HOC)。三者长得像(都是”包一层”),但意图完全不同——这是面试最爱考的辨析点。
核心知识点
1. 适配器模式:让不兼容的接口协同工作
适配器的本质是翻译——不改原有代码,加一个转换层把 A 接口适配成 B 接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 旧接口:返回 { name, age }
// 新接口:需要 { userName, userAge }
class OldUserAPI {
getUser() { return { name: '张三', age: 28 }; }
}
// 适配器:把旧接口翻译成新格式
class UserAdapter {
constructor(oldAPI) { this.oldAPI = oldAPI; }
getUser() {
const { name, age } = this.oldAPI.getUser();
return { userName: name, userAge: age }; // 字段映射
}
}
// 前端真实落点:统一多端数据源
function normalizeUser(data) {
// 不同后端返回不同字段名,统一成一套
return {
id: data.id ?? data.user_id,
name: data.name ?? data.nickname ?? data.userName
};
}
场景:对接多个第三方服务字段不一致、旧 API 重构后兼容老调用方、多端(iOS/Android/Web)数据格式统一。适配器让”改接口”不影响”用接口”的代码。
2. 代理模式:控制对目标的访问
代理和适配器的区别:代理和目标有相同接口,但代理在调用前后”插一脚”(拦截、校验、缓存、延迟):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ES6 Proxy:拦截对象操作
const user = { name: '张三', age: 28 };
const proxy = new Proxy(user, {
get(target, key) {
console.log(`读取 ${key}`);
return target[key];
},
set(target, key, value) {
if (key === 'age' && (value < 0 || value > 150)) {
throw new Error('年龄不合法');
}
target[key] = value;
}
});
proxy.name; // 打印 "读取 name"
proxy.age = 200; // 抛错:年龄不合法
// 图片懒加载:代理 img 的 src,进入视口才真正加载
function lazyImage(el) {
const realSrc = el.dataset.src; // 真实地址放 data-src
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) { el.src = realSrc; observer.unobserve(el); }
});
});
observer.observe(el);
}
Vue 3 的响应式就是 Proxy 的经典应用——拦截属性的读取和赋值,实现依赖收集和触发更新。
3. 装饰器模式:动态扩展功能
装饰器不改原对象,在外部包裹一层,动态叠加功能。前端的典型就是高阶组件(HOC):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 高阶组件:给任意组件"装饰"上 loading 能力
function withLoading(WrappedComponent) {
return function Enhanced({ loading, ...props }) {
if (loading) return <div>加载中...</div>;
return <WrappedComponent {...props} />;
};
}
const UserListWithLoading = withLoading(UserList);
// <UserListWithLoading loading={true} /> → 显示"加载中"
// <UserListWithLoading loading={false} data={...} /> → 正常渲染
// 函数式装饰:组合多个能力
const withAuth = fn => (...args) => {
if (!isLoggedIn()) throw new Error('未登录');
return fn(...args);
};
const withLog = fn => (...args) => {
console.log('调用参数', args);
return fn(...args);
};
const run = withLog(withAuth(businessLogic));
装饰器的精髓是组合优于继承——不用继承一个臃肿的基类,而是按需”贴”功能,任意组合。
4. 三者辨析:适配器 vs 代理 vs 装饰器
这是面试最高频的辨析题,关键是意图:
| 模式 | 核心意图 | 接口是否变化 | 典型例子 |
|---|---|---|---|
| 适配器 | 让不兼容的接口能用 | 接口变了(翻译格式) | 新旧 API 兼容层 |
| 代理 | 控制访问、增强控制 | 接口不变 | Vue 响应式、懒加载 |
| 装饰器 | 动态加功能 | 接口不变(增强) | HOC、middleware |
一句话记忆:适配器是”翻译官”(换接口)、代理是”门卫”(拦截访问)、装饰器是”贴膜”(加功能不改接口)。
5. 组合优于继承(Composition over Inheritance)
结构型模式背后的共同哲学是组合优先于继承:
1
2
3
4
5
6
7
8
9
// ❌ 继承:深度继承链,父类改动影响所有子类,且只能单继承
class Animal { eat() {} }
class Bird extends Animal { fly() {} }
class SwimBird extends Bird { swim() {} } // 想要"会游泳的鸟",继承链越来越深
// ✅ 组合:把能力拆成独立模块,按需拼装
const canFly = (obj) => ({ ...obj, fly: () => '飞' });
const canSwim = (obj) => ({ ...obj, swim: () => '游' });
const duck = canSwim(canFly({})); // 鸭子:会飞也会游
前端组件化(React/Vue)本身就是组合思想的极致体现——UI 由小组件拼装,而不是层层继承。结构型模式就是”用组合解决问题”的具体招式。
其实你每天都在用
- Vue 3 的响应式:
reactive()返回 Proxy,你obj.name = 'x'时被拦截、触发视图更新,这就是代理模式 - React 高阶组件:
withRouter、connect(mapState)给组件”贴”上路由/状态能力,就是装饰器 - 对接第三方 SDK 字段不一致:后端返回
user_id,前端统一normalize成id,就是适配器 - 图片懒加载 / 骨架屏:先用占位图代理真实图,滚动到视口才真正
src,就是代理 - axios 拦截器:请求前加 token、响应后统一处理错误,在”调用前后插一脚”,就是代理/装饰思想
常见误解(FAQ)
❌ 误区一:”适配器、代理、装饰器都是包一层,本质一样”
三者结构相似但意图天差地别:适配器是换接口(解决”不兼容”),代理是控访问(解决”何时/能否访问”),装饰器是加功能(解决”动态扩展”)。搞混意图,代码结构对了但设计就错了。面试答”都是包装器”只能拿一半分。
❌ 误区二:”装饰器模式就是高阶组件 HOC,一回事”
HOC 是装饰器思想在 React 里的一种实现,但不是唯一。装饰器还有:函数式中间件(withLog(withAuth(fn)))、ES 装饰器语法(@log)、AOP 切面。反过来,HOC 也不一定是装饰器——它也可能是代理(控制渲染)。模式是思想,HOC 是语法,别画等号。
❌ 误区三:”ES6 Proxy 和代理模式是同一个概念”
Proxy 是语言层面的 API(拦截对象操作的语法机制),代理模式是设计层面的思想(用中间对象控制访问)。Proxy 可以用来实现代理模式(Vue 响应式),但代理模式不一定要用 Proxy(懒加载用 IntersectionObserver 也是代理)。一个是工具,一个是思想。
❌ 误区四:”继承能实现代码复用,结构型模式是多余的”
继承带来的是强耦合 + 单继承限制 + 父类改动波及子类。组合(结构型模式的底层)则让能力独立、可复用、可任意拼装,且运行时也能动态组合。现代前端框架全部拥抱组合,继承只在”明确的 is-a 关系”下才用。结构型模式恰恰是”组合优于继承”的具体落地。
一句话总结
结构型模式的本质是用”组合 + 包一层”来代替”继承 + 改源码”——适配器换接口、代理控访问、装饰器加功能,三者共用一个招式,却各怀不同意图,掌握了”意图之别”才算真正掌握结构型模式。