设计模式:结构型模式
结构型模式关注的是:类和对象之间如何组合,如何让系统结构更灵活、更容易扩展。
创建型模式解决“对象怎么创建”,结构型模式解决“对象怎么组装”。
一、结构型模式总览
结构型模式主要解决以下问题:
- 类和对象之间如何组合成更大的结构
- 如何降低对象之间的耦合
- 如何在不修改原有代码的情况下扩展功能
- 如何让不兼容接口能够一起工作
- 如何减少大量对象带来的内存消耗
| 模式 | 核心思想 | 适合场景 |
|---|---|---|
| 适配器 Adapter | 让不兼容接口能一起工作 | 老接口适配新系统 |
| 桥接 Bridge | 抽象和实现分离 | 多维度变化 |
| 组合 Composite | 树形结构统一处理 | 文件夹、菜单、组织架构 |
| 装饰器 Decorator | 动态增强对象功能 | IO 流、功能叠加 |
| 外观 Facade | 给复杂系统提供简单入口 | 封装复杂子系统 |
| 享元 Flyweight | 共享对象减少内存 | 大量重复对象 |
| 代理 Proxy | 控制对目标对象的访问 | 权限、缓存、远程代理 |
结构型模式整体理解图
结构型模式接口适配适配器 Adapter多维度解耦桥接 Bridge整体部分统一组合 Composite功能动态增强装饰器 Decorator简化复杂系统外观 Facade共享减少内存享元 Flyweight控制对象访问代理 Proxy
二、适配器模式 Adapter
1. 核心思想
把一个类的接口转换成客户端想要的另一个接口。
简单说:
原来接口不兼容,加一个“转换头”。
生活例子:
- Type-C 转 USB 转接头
- 港版插头转国标插座
- 读卡器把 SD 卡转换成 USB 接口
2. 适合场景
- 旧系统接口不能直接修改
- 新系统需要使用旧类的功能
- 第三方接口和自己系统接口不一致
- 想复用已有代码,但接口不匹配
3. 配图理解
客户端需要 TypeC 接口
UsbToTypeCAdapter 适配器
UsbCharger 旧接口
完成 USB 充电
4. Java 示例
java
代码解读
复制代码
interface TypeC { void chargeWithTypeC(); } class UsbCharger { public void chargeWithUsb() { System.out.println("使用 USB 充电"); } } class UsbToTypeCAdapter implements TypeC { private UsbCharger usbCharger; public UsbToTypeCAdapter(UsbCharger usbCharger) { this.usbCharger = usbCharger; } public void chargeWithTypeC() { System.out.println("适配器转换 Type-C 到 USB"); usbCharger.chargeWithUsb(); } } public class Main { public static void main(String[] args) { UsbCharger usbCharger = new UsbCharger(); TypeC adapter = new UsbToTypeCAdapter(usbCharger); adapter.chargeWithTypeC(); } }
5. 优缺点
优点:
- 可以复用旧代码
- 解决接口不兼容问题
- 客户端不需要知道适配细节
- 符合开闭原则,可以在不改旧类的情况下新增适配能力
缺点:
- 适配器太多会让系统结构复杂
- 只是接口转换,不应该滥用
- 如果旧接口设计很差,适配器只能缓解问题,不能彻底解决问题
6. 记忆口诀
适配器:接口不合,加个转换器。
三、桥接模式 Bridge
1. 核心思想
将抽象部分和实现部分分离,使它们可以独立变化。
简单说:
两个维度都在变化时,不要疯狂继承,而是用组合把它们桥接起来。
2. 适合场景
适合两个或多个维度都在变化的场景。
例如手机有两个变化维度:
- 品牌:华为、小米、苹果
- 颜色:黑色、白色、蓝色
如果不用桥接,可能要写:
- 黑色华为手机
- 白色华为手机
- 蓝色华为手机
- 黑色小米手机
- 白色小米手机
- 蓝色小米手机
类数量会迅速膨胀。
3. 配图理解
4. Java 示例
java
代码解读
复制代码
interface Color { String getColor(); } class Black implements Color { public String getColor() { return "黑色"; } } class White implements Color { public String getColor() { return "白色"; } } abstract class Phone { protected Color color; public Phone(Color color) { this.color = color; } public abstract void show(); } class HuaweiPhone extends Phone { public HuaweiPhone(Color color) { super(color); } public void show() { System.out.println(color.getColor() + " 华为手机"); } } class XiaomiPhone extends Phone { public XiaomiPhone(Color color) { super(color); } public void show() { System.out.println(color.getColor() + " 小米手机"); } } public class Main { public static void main(String[] args) { Phone phone = new HuaweiPhone(new Black()); phone.show(); } }
5. 优缺点
优点:
- 避免类爆炸
- 抽象和实现可以独立扩展
- 比继承更加灵活
- 符合组合优于继承的思想
缺点:
- 理解难度比简单继承高
- 需要正确识别系统中的多个变化维度
- 设计过度时会让简单问题复杂化
6. 记忆口诀
桥接模式:两个维度,各自变化,中间架桥。
四、组合模式 Composite
1. 核心思想
把对象组织成树形结构,让客户端可以用统一方式处理单个对象和组合对象。
简单说:
单个对象和一组对象,对外表现得像同一种东西。
2. 适合场景
- 文件和文件夹
- 公司部门和员工
- 菜单和子菜单
- 树形目录
- 组织架构
3. 配图理解
根目录 Folder
文件 a.txt
图片文件夹 Folder
1.png
2.png
文档文件夹 Folder
方案.docx
4. Java 示例
java
代码解读
复制代码
import java.util.ArrayList; import java.util.List; interface FileSystemNode { void show(); } class FileNode implements FileSystemNode { private String name; public FileNode(String name) { this.name = name; } public void show() { System.out.println("文件:" + name); } } class FolderNode implements FileSystemNode { private String name; private List<FileSystemNode> children = new ArrayList<>(); public FolderNode(String name) { this.name = name; } public void add(FileSystemNode node) { children.add(node); } public void show() { System.out.println("文件夹:" + name); for (FileSystemNode child : children) { child.show(); } } } public class Main { public static void main(String[] args) { FolderNode root = new FolderNode("根目录"); root.add(new FileNode("a.txt")); FolderNode img = new FolderNode("图片"); img.add(new FileNode("1.png")); img.add(new FileNode("2.png")); root.add(img); root.show(); } }
5. 优缺点
优点:
- 适合树形结构
- 客户端可以统一处理叶子节点和容器节点
- 扩展方便
- 层级结构清晰
缺点:
- 对类型限制不够严格
- 树形结构太复杂时调试困难
- 如果叶子对象和容器对象差异太大,统一接口可能会显得勉强
6. 记忆口诀
组合模式:整体和部分,一样对待。
五、装饰器模式 Decorator
1. 核心思想
在不改变原有对象结构的情况下,动态地给对象增加新的功能。
简单说:
原来的对象不改,用一层一层“包装”来增强功能。
生活例子:
- 奶茶加珍珠、加椰果、加布丁
- 咖啡加牛奶、加糖、加冰
- 手机壳给手机增加防摔、支架、卡包功能
2. 适合场景
- 想给对象动态增加功能
- 不想通过继承创建大量子类
- 功能可以一层一层叠加
- Java IO 流,例如 BufferedInputStream 包装 FileInputStream
3. 配图理解
普通咖啡
牛奶装饰器
糖装饰器
普通咖啡 + 牛奶 + 糖
4. Java 示例
java
代码解读
复制代码
interface Coffee { String getDescription(); double cost(); } class SimpleCoffee implements Coffee { public String getDescription() { return "普通咖啡"; } public double cost() { return 10; } } abstract class CoffeeDecorator implements Coffee { protected Coffee coffee; public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; } } class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return coffee.getDescription() + " + 牛奶"; } public double cost() { return coffee.cost() + 3; } } class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return coffee.getDescription() + " + 糖"; } public double cost() { return coffee.cost() + 1; } } public class Main { public static void main(String[] args) { Coffee coffee = new SimpleCoffee(); coffee = new MilkDecorator(coffee); coffee = new SugarDecorator(coffee); System.out.println(coffee.getDescription()); System.out.println("价格:" + coffee.cost()); } }
5. 优缺点
优点:
- 比继承更加灵活
- 可以动态增加功能
- 多个装饰器可以自由组合
- 符合开闭原则
缺点:
- 装饰层数太多时,代码理解成本会变高
- 调试时不容易看清楚对象被包装了几层
- 如果装饰器设计不好,容易让结构复杂化
6. 记忆口诀
装饰器模式:不改原对象,外面套功能。
六、外观模式 Facade
1. 核心思想
给复杂子系统提供一个简单统一的入口,让客户端不用关心内部复杂细节。
简单说:
里面很复杂,外面给你一个简单按钮。
生活例子:
一键开启影院模式:
- 关灯
- 打开电视
- 打开音响
- 拉上窗帘
用户不需要一个一个操作,只需要点击“影院模式”。
2. 适合场景
- 子系统很复杂
- 客户端不想直接接触很多类
- 想降低客户端和子系统之间的耦合
- 给旧系统封装一个简单入口
3. 配图理解
客户端
HomeTheaterFacade 外观类
灯光系统
电视系统
音响系统
窗帘系统
4. Java 示例
java
代码解读
复制代码
class Light { public void off() { System.out.println("关灯"); } } class TV { public void on() { System.out.println("打开电视"); } } class SoundSystem { public void on() { System.out.println("打开音响"); } } class Curtain { public void close() { System.out.println("拉上窗帘"); } } class HomeTheaterFacade { private Light light; private TV tv; private SoundSystem soundSystem; private Curtain curtain; public HomeTheaterFacade() { this.light = new Light(); this.tv = new TV(); this.soundSystem = new SoundSystem(); this.curtain = new Curtain(); } public void watchMovie() { System.out.println("开启影院模式"); light.off(); curtain.close(); tv.on(); soundSystem.on(); } } public class Main { public static void main(String[] args) { HomeTheaterFacade facade = new HomeTheaterFacade(); facade.watchMovie(); } }
5. 优缺点
优点:
- 简化客户端调用
- 降低客户端和子系统之间的耦合
- 隐藏系统内部复杂细节
- 让系统更容易使用
缺点:
- 外观类可能会变得很庞大
- 如果外观类封装得太死,灵活性会下降
- 不适合替代所有子系统接口
6. 记忆口诀
外观模式:复杂系统,给个简单门面。
七、享元模式 Flyweight
1. 核心思想
通过共享对象来减少内存占用。
简单说:
相同的对象不要重复创建,能共用就共用。
生活例子:
游戏地图里有很多树:
- 树的类型、颜色、图片资源可能是一样的
- 但是每棵树的位置不同
如果每棵树都保存完整数据,会非常浪费内存。享元模式会把相同的部分共享起来,只把不同的部分单独保存。
2. 适合场景
- 系统中有大量相似对象
- 对象创建成本高
- 很多对象的内部状态可以共享
- 游戏场景、字符串常量池、缓存池、连接池
3. 关键概念
享元模式一般会把对象状态分成两类:
- 内部状态:可以共享,例如树的类型、颜色、图片
- 外部状态:不能共享,例如树的位置坐标
4. 配图理解
5. Java 示例
java
代码解读
复制代码
import java.util.HashMap; import java.util.Map; class TreeType { private String name; private String color; public TreeType(String name, String color) { this.name = name; this.color = color; } public void display(int x, int y) { System.out.println("树类型:" + name + ",颜色:" + color + ",位置:(" + x + "," + y + ")"); } } class TreeFactory { private static Map<String, TreeType> treeTypes = new HashMap<>(); public static TreeType getTreeType(String name, String color) { String key = name + "_" + color; if (!treeTypes.containsKey(key)) { treeTypes.put(key, new TreeType(name, color)); System.out.println("创建新的树类型:" + key); } return treeTypes.get(key); } } class Tree { private int x; private int y; private TreeType treeType; public Tree(int x, int y, TreeType treeType) { this.x = x; this.y = y; this.treeType = treeType; } public void display() { treeType.display(x, y); } } public class Main { public static void main(String[] args) { TreeType greenTree = TreeFactory.getTreeType("松树", "绿色"); Tree tree1 = new Tree(10, 20, greenTree); Tree tree2 = new Tree(30, 40, greenTree); Tree tree3 = new Tree(50, 60, greenTree); tree1.display(); tree2.display(); tree3.display(); } }
6. 优缺点
优点:
- 减少对象数量
- 节省内存
- 适合大量重复对象场景
- 可以提高系统性能
缺点:
- 代码复杂度会增加
- 需要区分内部状态和外部状态
- 如果对象差异很大,就不适合使用
7. 记忆口诀
享元模式:共享重复对象,省内存。
八、代理模式 Proxy
1. 核心思想
给目标对象提供一个代理对象,由代理对象控制对目标对象的访问。
简单说:
你不直接找本人,而是先找代理人。
生活例子:
- 明星经纪人
- 房产中介
- 代购
- 访问网站时的代理服务器
代理对象可以在真正调用目标对象之前或之后,增加额外逻辑,比如权限校验、缓存、日志、延迟加载。