老王要装修一栋智能公寓,他发现整个装修过程就像搭积木:老房子的插座和新电器接口对不上(适配器)、墙面不需要砸掉也能一层层变美(装饰器)、出差时还需要一个管家替他应付访客(代理)、玄关的一块智能面板能一键控制全屋(外观)、灯具和灯泡可以随意自由搭配(桥接)、家族族谱是一棵可以灵活遍历的大树(组合)、小区公共会议室让所有人共享省下巨额成本(享元)。这七种“搭房子”的智慧,正是软件工程里赫赫有名的结构型设计模式——它们不关心对象怎么被创建,只关心对象如何组合、连接、共享,让系统在需求变化时依然结实又灵活。
对 AI 从业者而言,结构型模式是你把代码模块装配成可维护系统的基本功:写模型网关、做推理缓存、封装数据管道,全靠这七种模式的手感。学会它,你的工程不再“拧巴”。
目录
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
适配器模式——“转接头”的不动墙解决方案
老王从欧洲淘回一台智能音箱,兴奋地拆箱,却发现插头是两脚圆头,家里插座是三脚扁孔。快递师傅笑着递给他一个白色小转接头——不用改插座,不用拆音箱,插上就通电。适配器模式(Adapter Pattern)和这个转接头做的事情一模一样。
在软件世界里,新老模块的“插头”和“插座”经常对不上:一个 AI 项目要同时接入三家大模型 API,但每家 SDK 的调用方法长得完全不同。适配器模式的做法是:不改造任何一方,而是引入一个中间翻译层,把一个类的接口转换成客户端期望的另一个接口,让原本无法协作的类顺利合作。
实现上分两种:对象适配器通过组合持有被适配者的引用,灵活且是主流;类适配器通过多继承实现,在支持多继承的语言中使用,Java 中较少用到。无论哪种,客户端看到的始终是一个统一、自己熟悉的方法。
老王的转接头安然地待在墙上,往后每来一件新电器,只需换一个转接头,墙上的插座永远不用动。适配器模式的精髓正是这种“不改造双方、只增加中间翻译层”的优雅——它是系统面对外部变化时最廉价的那堵缓冲墙。
- 客户端依赖适配器客户端只与 Adapter 暴露的目标接口打交道,引用方向指向 Adapter。
- 适配器翻译请求Adapter 持有一个 Adaptee 引用,当目标方法被调用时,内部把调用翻译成 Adaptee 的原生方法。
- 接口语义转换客户端调用的 request() 被映射成 adaptee 的内部方法 specificRequest(),就像 220V 转 110V。
装饰器模式——“软装叠加”的动态魔法
装修师傅对老王拍胸脯:不用砸墙。咱们挂画、贴墙纸、加灯带,一层层叠上去,房子就变高级了。想换风格?撕掉墙纸再贴新的,承重墙毫发无损。装饰器模式(Decorator Pattern)就是这种“软装思维”。
它解决的是一个非常现实的痛点:如果房子要加隔音、加投影、加地暖,总不能每加一种功能就砸墙重砌一种“新墙”吧?面向对象世界里,“砸墙重砌”就是继承——为每一种功能组合创建一个子类,组合一多,类就爆炸。装饰器模式改用叠加:在不改变现有对象结构的前提下,动态地给对象添加新职责。
它的机制很巧妙:装饰器和原始对象实现同一个接口,装饰器内部又持有原始对象。调用时先执行装饰器新增的逻辑,再执行原对象逻辑,就像一层层包上外壳。由于接口一致,客户端感受不到装饰层的存在,这就是“透明装饰”;如果装饰器还额外暴露了新增方法,称为“半透明装饰”。
老王看着一层层变美的房子,完全不需要知道哪一层是哪天加的。装饰器模式让系统拥有了“装修队”式的灵活性:随时加一层、撕一层,主结构永远稳定。这正是它比继承高明的地方——继承是图纸上的设计,装饰器是施工现场的即兴发挥。
- 基础方法base() 是对象最初就具备的核心能力,相当于毛坯房的墙面。
- 第一层装饰f₁ 接收 base() 的结果,先做自己的增强再返回,相当于第一层乳胶漆。
- 多层叠加每层装饰都包住前一层,最终效果由外向内逐层展开,得到所有增强的累加结果。
代理模式——“管家”的过滤智慧
老王白天在公司忙,装修工、快递员、推销员轮番按门铃。于是他把钥匙交给管家小刘,叮嘱一句:重要的事转达给我,不重要的你看着办。小刘站在门口,先盘问来者意图,再决定是否打扰老王。这就是代理模式(Proxy Pattern)的日常生活原型。
代理模式的核心是为其他对象提供一种替身或占位符,以控制对原对象的访问。它和装饰器长得像,但目的不同:装饰器是“增强功能”,代理是“控制访问”。代理和原对象实现同一接口,客户端持有的其实只是代理,真正干活的默默躲在后面。
常见的有三类:虚拟代理延迟真实对象的创建,直到真要用到资源时才实例化(例如加载超大图片);保护代理在调用前做权限校验,拦截非法请求;远程代理屏蔽网络传输细节,让本地代码像调本地方法一样调用远方服务。额外,代理也常被用来实现日志、计数、缓存等横切关注点。
老王出差一个月回来,管家小刘把厚厚一沓访客记录递上来,老王只翻了翻重点那几条,剩下的全是管家替他挡掉的噪音。代理模式的价值就在于此:它把“打扰”与“核心”隔离,让客户端面对的资源更干净、更安全、也更快。
- 请求到达代理客户端把请求 r 交给代理,而不是直接触碰真实对象,代理是整个交互的守门员。
- 策略判定代理依据权限表、资源是否加载完成等条件,返回是否放行的二值判定。
- 转发真实调用通过检查后,代理才调用 RealSubject 的真正实现,并把结果原样返回客户端。
外观模式——“智能中控面板”的一键魔法
老王在玄关装了一块智能面板,上面只有几个按钮:“回家模式”“离家模式”“影院模式”。按下“离家模式”,全屋灯光熄灭、空调关闭、窗帘拉上、安防布防——这一切只需要一次触碰。这就是外观模式(Facade Pattern)在生活中的投影。
它的作用是为一组复杂的子系统接口提供一个统一的高层接口。老王不需要知道灯的型号、空调的状态机、窗帘电机的协议,他只需要认识这块面板。外观类把子系统的各个调用编排成业务动作,客户端只依赖外观类,从而大大降低耦合。
外观模式的另一大贡献是切实践行了最少知识原则(也叫迪米特法则):不与陌生人说话。客户端不要和一堆子系统类打交道,只跟外观这个“老熟人”沟通就好。要注意,外观类并没有禁止客户端访问子系统——它提供的是“便捷通道”,而不是“铁笼子”。
按下“离家模式”的那一刻,老王感受到的是一种被简化后的确定感:无论背后多复杂,入口只有一个,结果总是对的。外观模式教会我们:系统越庞大,越需要主动向外部世界收敛自己的“脸面”,把复杂留给内部,把简单交还用户。
- 多个子系统职责每个子系统(灯、空调、窗帘、安防)各有自己的执行方法,互不感知。
- 外观编排动作外观类把多个子系统调用按业务顺序复合为一个高层方法 F,作为组合入口。
- 客户端只碰外观客户端只调用 F,不引用任何子系统对象,耦合面收窄到最低。
桥接模式——“灯与灯泡”的自由组合
老王逛灯具城,发现灯具分成吊灯、吸顶灯、壁灯(造型维度),灯泡分成 LED、白炽灯、智能调色灯(光源维度)。销售员热情地说:任选组合,自由搭配!老王突然意识到,如果每一种搭配都做成单独的商品,店里要摆 3×3=9 盏样品;而把造型和灯泡分开陈列,只需要 3 盏灯 + 3 种灯泡。
这正是桥接模式(Bridge Pattern)要拆解的难题:当一个类有两个独立变化的维度时,直接使用继承会产生类数量爆炸——M 种抽象 × K 种实现 = M×K 个类。桥接模式的解法简单而深刻:将抽象部分与实现部分分离,让它们各自独立演化,再用一个“桥”连接起来。
具体做法是:抽象类持有实现部分的接口引用,而不是继承具体实现。运行时客户端决定往抽象里注入哪一种实现。这样抽象维度可以扩展为新增的灯具样式,实现维度可以扩展为新型灯泡,互不干扰、互不拖累,充分体现“组合优于继承”的设计信条。
老王最终选了吊灯 + 智能调色灯,一个月后想换吸顶灯,只需要换灯不换灯泡。桥接模式的智慧正在于:识别出系统中那些“各自会变”的维度,把它们从继承的混凝土中拆开,再通过一座灵巧的桥重新连接——变化被隔离,结构却依然牢固。
- 继承膨胀两个维度两两组合,类数量按乘积增长,系统迅速臃肿。
- 分离为两条链抽象维度只保留 M 个造型类,实现维度只保留 K 个光源类,各自独立演化。
- 桥接组合注入抽象层内部持有实现层接口引用,运行时组装,从乘法降维到加法。
组合模式——“家族族谱”的递归智慧
老王翻家谱:爷爷在顶端,下面有父亲和叔伯两个孩子分支,父亲下面又有老王和兄弟姐妹。整个家族是一棵树。办家宴时,老王不需要区分“这是个长辈”还是“这是个平辈”——每一个分支都被统一当作“亲戚”对待,族长一声令下,整个大家族递归通知到位。
组合模式(Composite Pattern)将对象组织成树形结构,并让用户对单个对象(叶子)和组合对象(容器)的使用具有一致性。文件系统是最经典的例子:文件夹里有文件夹和文件,用户删除目录时,操作系统递归地把里面所有东西一起删除。
它的结构核心是组件接口(Component),叶子节点(Leaf)和容器节点(Composite)都实现它。容器持有子组件列表并提供 add/remove 方法。透明组合让接口包含管理方法,叶子也要实现它们(但方法体为空或抛异常),好处是客户端统一处理;安全组合则不把管理方法放进公共接口,叶子更安全但需要类型判断。二者各有权衡。
族长通知家宴时,绝不会为“你去找三个叔叔 + 四个堂弟”分别发消息,他只要通知儿子们,儿子再通知孙子们,信息便顺着树的纹路流遍全族。组合模式让递归变成一种日常:用统一的接口,简化了一个层次的复杂性。
- 叶子节点族谱中的单个成员(老王)是不可再分的基本元素,计数为 1。
- 容器节点爷爷这种分支节点包含若干子节点,自身大小是子节点大小的总和。
- 递归统一处理整个家族的规模逐层下探到所有叶子,得到树的总节点数,整个过程无需区分叶子与容器。
享元模式——“共享会议室”的极致复用
老王小区配套了一位精明的物业经理。他发现很多住户都反映“家里来客人没地方招待”,但不能给每户都建会议室啊!于是他改建了一间公共会议室,谁要开家庭聚会就预约使用。空间共用、维护共用,成本瞬间下来一大截。这就是享元模式(Flyweight Pattern)的现实样本。
享元模式运用共享技术有效地支持大量细粒度对象的复用。它的关键洞察是:一个对象的状态可以拆成两部分。内部状态(intrinsic state)是稳定不变、可以共享的——比如会议室的场地本身;外部状态(extrinsic state)是随上下文变化的、由客户端传入的——比如预约的日期、使用的人名。
享元工厂(FlyweightFactory)维护一个共享池:当请求一个享元对象时,若池中已有相同内部状态的对象,直接返回复用,否则创建新对象再放入池中。这样 N 个逻辑对象可能只对应极少数物理实例,极大降低内存消耗。典型应用包括字符串常量池、线程池、游戏中的树叶渲染等。
老王看着物业公告栏上满满的会议室预约表,心里默默算账:以前 50 个家庭都要建会议室得花几百万,现在一间会议室转起来,大家各用各的时间段,互不影响。享元模式告诉我们,世界上很多浪费来自“人人拥有”的幻觉——把不变的东西交给大家共享,把变化的东西留给个人,成本与效率的平衡就这样被解决了。
- 内部状态共享N 个逻辑请求共享 N_f 个物理实例,内部状态只存一份,N_f 远小于 N。
- 外部状态外置每个对象的内存占用量拆成内部与外部两部分,外部状态由客户端各自持有,不占共享池。
- 总节省量逻辑对象总数越大、内部状态占比越高,内存节省越显著,这就是享元的规模效应。
AI 应用场景
场景一:AI 模型统一网关(适配器模式)
阶段:模型集成与接口统一
解决的问题:AI 应用常需切换 OpenAI、Claude、文心等多家大模型,各家 SDK 的调用方式不同。传统做法是每个模型写一套调用代码,业务层跟着改,维护成本高。用适配器模式为每家 SDK 封装一个统一 chat(prompt) 接口,业务层面对接口编程,换模型只换适配器。
具体案例:某客服机器人团队需在多个大模型间做动态路由。他们定义了一个统一 LLMClient 接口,用 OpenAIAdapter、ClaudeAdapter 分别包装官方 SDK,路由层只依赖统一接口,一周内完成了三家模型的平滑接入。
场景二:推理服务共享模型权重(享元模式)
阶段:模型推理与部署优化
解决的问题:大模型推理服务中,模型权重动辄几十上百 GB,如果每个请求都加载一份副本,显存立刻爆掉。享元模式把模型权重当作“共享池”——整体常驻显存只存一份,每个请求只需携带自己的上下文(外部状态)与共享权重结合。
具体案例:某云端推理平台使用 vLLM 部署 70B 模型,数千个并发请求共享同一份 KV Cache 管理策略和权重实例,显存利用率提升 5 倍,单卡吞吐量翻倍。
场景三:数据管道动态增强(装饰器模式)
阶段:数据预处理与特征工程
解决的问题:训练前要依次给数据加载器加缓存、随机增强、打乱、混合精度转换。传统做法是修改 Loader 源码或写大量子类。装饰器模式为 Loader 的 next_batch() 逐层包裹能力,任意开关组合而不污染原始实现。
具体案例:某视觉团队构建了 CachedLoader → AugmentLoader → ShuffleLoader 三级装饰链,跑通一次完整训练只需替换装饰器层序,实验自由度大幅提升。
Python 演示
"""结构型模式综合演示:用适配器统一 AI 模型接口,再用装饰器动态加日志。运行:python demo.py"""from functools import wraps class LLMClient: """统一接口:所有模型的客户端都长这样""" def chat(self, prompt): raise NotImplementedError class OpenAIAdapter(LLMClient): def __init__(self, api): self.api = api def chat(self, prompt): # 把统一 chat 翻译成 OpenAI 原生调用 return self.api.create_completion(prompt) class ClaudeAdapter(LLMClient): def __init__(self, api): self.api = api def chat(self, prompt): # 把统一 chat 翻译成 Claude 原生调用 return self.api.send_message(prompt) def with_logging(client): """装饰器:不改原类,给 chat 动态加一层日志""" @wraps(client.chat) def chat(prompt): print("[log] prompt:", prompt) return client.chat(prompt) client.chat = chat return client class MockOpenAI: def create_completion(self, prompt): return "OpenAI reply: " + prompt class MockClaude: def send_message(self, prompt): return "Claude reply: " + prompt if __name__ == "__main__": clients = [ OpenAIAdapter(MockOpenAI()), ClaudeAdapter(MockClaude()), ] for client in clients: chat = with_logging(client).chat print(chat("hello"))
chat() 接口,以及装饰器如何在不修改原类的前提下为调用动态添加日志。def chat(self, prompt)统一接口约定,所有适配器必须实现这个签名,客户端只认它。return self.api.create_completion(prompt)适配器内部把统一调用“翻译”成各家 SDK 的原生方法。def with_logging(client)装饰器的核心:接收一个客户端,返回一个被包裹的新方法,原类毫发无损。client.chat = chat展示了 Python 中装饰器模式的动态替换本质:运行时给实例“贴”一层新行为。if __name__ == "__main__":主入口,依次演示两家模型的统一调用和日志增强效果。
常见误区
误区适配器跟装饰器差不多吧?都是包一层类,都是把别人封装一下。
正解它们的动机截然相反:适配器是“接口不兼容,包一层改接口”——像电源转接头;装饰器是“接口保持不变,包一层加能力”——像墙上挂画,画挂上后墙面还是那面墙。用错的话,系统会多出一层毫无意义的“翻译”,或者把该保留的统一接口破坏掉。
为何常见:两者结构上都是“持有另一个对象并转发调用”,初学者盯着 UML 图看,一眼看上去都是那几条线,容易混淆动机层面。
误区代理模式就是装饰器的变体,都是先调用自己再调用别人,可以互相替换。
正解代理控制访问,装饰器增强功能。代理对目标对象的生命周期负责(比如虚拟代理负责按需创建),装饰器则只关心“多叠一层”。用保护代理实现的功能若换成装饰器,职责就错位了——装饰器没有权限做“拒绝请求”的决定,它只负责“做完自己的动作接着往下传”。
为何常见:两者都实现了同一个接口且内部持有目标对象,结构雷同度极高,新手容易只记住了形状、忘了意图。
误区透明组合让叶子也有 add/remove 方法,统一接口多好,应该到处都用,安全组合是多余的。
正解透明组合的代价是叶子暴露了毫无意义的方法,调用 add 会抛异常或静默失败,一旦客户端误用便产生隐蔽 bug。真正的工程判断是:如果客户端大概率要区别对待叶子与容器,用安全组合并在需要时类型判断;只有在“完全统一处理”的诉求占主导时才用透明组合。
为何常见:设计模式书上常画“统一处理”的示意图,让人误以为“统一”永远优于“安全”。
误区桥接模式不就是抽象类里放一个接口引用吗?我看很多代码都这样写,所以它们都是桥接。
正解桥接的前提是两个维度都要独立变化。如果只有一个变化维度,这层接口就只是普通的多态,不是桥接。真正的桥接要先做维度分析:抽象层会变吗?实现层会变吗?确认“类数量会随两维度乘积膨胀”这个问题成立,桥接才有用武之地,否则只会增加无谓的间接层。
为何常见:模式的名字让人直觉联想到“连接”,于是把一切持有接口的写法都叫桥接,忽略了它要根治的具体症状——乘法级类爆炸。
误区享元模式就是全局只创建一个对象,所有人都用这一个实例,跟单例模式一回事。
正解单例是“全局唯一”,享元是“池化复用”。享元工厂可以同时维护多种内部状态的对象(比如不同颜色的字符图标各一份),它们构成一个共享池;单例永远只有一份实例。享元真正的核心是内/外部状态分离,而不是数量为 1。
为何常见:实际工程中享元常常被最简单的“一个池子”所代表,很多文章也拿“共享”一笔带过,让人误以为它等同于单例。
面试题
Q1. 下列哪个结构型模式的核心是“将不兼容的接口转换成客户端期望的接口”?
- A.适配器模式← 正确答案
- B.装饰器模式
- C.代理模式
- D.外观模式
答案:A。适配器模式就像电源转接头,专门解决接口对不上的问题。装饰器是保持接口加功能,代理是控制访问,外观是提供统一入口,都不改接口语义。
Q2. 以下关于装饰器模式的说法,正确的有?
- A.可以在运行时动态添加职责← 正确答案
- B.它是通过继承实现扩展的
- C.它对客户端保持接口透明← 正确答案
- D.它只能添加一种固定功能
答案:AC。装饰器模式使用组合而非继承,在运行时逐层包裹对象来添加职责,且因为与原始对象实现同一接口,客户端感知不到装饰层存在。它支持多层叠加多种功能,不是固定一两种。
Q3. 外观模式的核心思想是?
- A.让子系统之间互相调用
- B.为复杂子系统提供统一入口← 正确答案
- C.让客户端直接操作所有类
- D.用继承扩展子系统功能
答案:B。外观模式像玄关的智能面板,客户端只按一个按钮,内部灯光空调窗帘安防的复杂操作全被封装。它的本质是收敛入口,不是开放子系统或改名继承。
Q4. 虚拟代理最主要的应用价值是?
- A.控制用户访问权限
- B.延迟创建开销大的真实对象← 正确答案
- C.记录所有调用日志
- D.转换不兼容的接口
答案:B。虚拟代理像“先占个门牌号”,等真正要用时再去创建真实对象,常用于大图片、大模型等重资源。权限控制是保护代理,日志是日志代理,转换接口是适配器。
Q5. 某系统有两个独立变化的维度,直接用继承导致类数量乘积式膨胀,应优先采用?
- A.适配器模式
- B.享元模式
- C.桥接模式← 正确答案
- D.组合模式
答案:C。桥接模式专门针对多维独立变化,把 M×K 的继承爆炸化解为 M+K 的组合结构,像灯具造型与灯泡光源自由搭配。适配器解决接口不兼容,享元解决对象过多,组合解决树形结构。
Q6. 组合模式中,把 add/remove 等管理方法也放进公共接口的设计称为?
- A.安全组合
- B.透明组合← 正确答案
- C.动态组合
- D.静态组合
答案:B。透明组合让叶子节点也能“看到”管理方法,对客户端完全透明但存在调用不适配方法的隐患;安全组合把管理方法放在容器类中,牺牲透明换取安全。
Q7. 下列哪些属于结构型设计模式?
- A.桥接模式← 正确答案
- B.观察者模式
- C.享元模式← 正确答案
- D.策略模式
答案:AC。结构型模式关注对象如何组合与共享,包括适配器、装饰器、代理、外观、桥接、组合、享元七种。观察者属于行为型,策略也属于行为型,它们的关注点是对象间的职责分配。
Q8. 享元模式中,可以被多个对象共享的稳定状态称为?
- A.外部状态
- B.即时状态
- C.内部状态← 正确答案
- D.临时状态
答案:C。内部状态是稳定不变、可被共享的部分,比如会议室场地本身;外部状态由客户端传入、随请求变化,比如预约时间。共享池存放的是内部状态。
Q9. 需要给一个已有的 Python 类方法动态添加调用日志且不修改原类,最合理的两个做法是?
- A.装饰器模式← 正确答案
- B.外观模式
- C.Python 函数装饰器← 正确答案
- D.继承重写
答案:AC。装饰器模式与 Python 的 @decorator 语法天然契合,都是绕开原类动态包裹行为。外观模式做的是统一入口,继承重写需要提前知道组合且破坏封装,不如装饰器灵活。
Q10. 千亿参数大模型推理服务中,模型权重常驻显存被所有请求共享,每个请求只携带自己的 prompt 上下文,这最直接体现了哪种模式的运用?
- A.享元模式← 正确答案
- B.桥接模式
- C.组合模式
- D.代理模式
答案:A。模型权重相当于享元中的“内部状态”——稳定、共享、只存一份;每个请求的 prompt 是“外部状态”——变化、独立携带。这正是享元的典型形态,其他模式在此场景中均非主导。