1. 结构型设计模式概述
结构型设计模式是Java设计模式中负责处理对象组合的重要类别,它关注如何将类或对象按某种布局组成更大的结构。与创建型模式关注对象创建、行为型模式关注对象交互不同,结构型模式的核心在于通过继承和组合构建更灵活、高效的对象结构。
我在实际项目中最常遇到的场景是:当系统需要新增功能时,发现直接修改原有类会导致代码臃肿或破坏开闭原则。这时结构型模式就能大显身手——它们提供了一种不直接修改原有代码却能扩展功能的优雅方案。比如需要给现有类添加新行为时,装饰器模式比继承更灵活;需要处理不同接口的兼容问题时,适配器模式比硬编码更可靠。
结构型模式共包含7种经典实现,每种都针对特定的结构问题:
- 适配器模式(Adapter):解决接口不兼容问题
- 桥接模式(Bridge):分离抽象与实现
- 组合模式(Composite):处理树形结构
- 装饰器模式(Decorator):动态添加职责
- 外观模式(Facade):简化复杂子系统
- 享元模式(Flyweight):共享细粒度对象
- 代理模式(Proxy):控制对象访问
提示:结构型模式常被误认为是"代码优化技巧",实际上它们解决的是系统扩展性和维护性的架构级问题。比如用组合模式处理递归结构,比硬编码树形遍历更符合开闭原则。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 适配器模式:接口转换的艺术
2.1 现实场景中的适配需求
去年我在开发支付系统时遇到典型案例:系统原本只支持支付宝支付(Alipay接口),现在需要接入微信支付(WeChatPay接口)。两个SDK的方法签名完全不同:
java复制// 原有支付宝接口
public class Alipay {
public void alipayPay(BigDecimal amount) { ... }
}
// 新增微信接口
public class WeChatPay {
public void wechatPay(int cents) { ... }
}
直接修改业务代码会导致大量if-else分支。这时适配器模式的价值就显现出来了——它像电源转接头一样,让不兼容的接口能够协同工作。
2.2 类适配器实现
通过继承实现适配是经典做法:
java复制public class WeChatPayAdapter extends WeChatPay implements Payment {
@Override
public void pay(BigDecimal amount) {
// 金额转换:元转分
super.wechatPay(amount.multiply(new BigDecimal(100)).intValue());
}
}
关键点在于:
- 适配器继承被适配者(WeChatPay)
- 同时实现目标接口(Payment)
- 在适配方法中处理参数转换
注意:Java单继承的限制使得类适配器不够灵活,当需要适配多个类时会遇到困难。
2.3 对象适配器实践
更推荐使用对象组合方式:
java复制public class WeChatPayAdapter implements Payment {
private WeChatPay wechatPay;
public WeChatPayAdapter(WeChatPay wechatPay) {
this.wechatPay = wechatPay;
}
@Override
public void pay(BigDecimal amount) {
wechatPay.wechatPay(amount.multiply(new BigDecimal(100)).intValue());
}
}
这种实现的优势在于:
- 可以适配多个不同的支付SDK
- 符合组合优于继承原则
- 更容易进行单元测试
2.4 适配器模式在JDK中的应用
Java集合框架中的Arrays.asList()就是典型适配器,它将数组适配为List接口:
java复制String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr); // 适配器实现
另一个例子是IO流中的InputStreamReader,它把字节流适配为字符流:
java复制InputStream is = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(is, "UTF-8"); // 适配器
3. 装饰器模式:动态扩展功能
3.1 咖啡店的装饰哲学
想象一个咖啡订单系统:基础咖啡4元,加牛奶0.5元,加糖0.2元,加奶油0.8元。如果用继承实现所有组合,会产生类爆炸(美式咖啡+牛奶、美式咖啡+糖、美式咖啡+牛奶+糖...)。
装饰器模式的解决方案是:
java复制// 组件接口
public interface Coffee {
BigDecimal getCost();
String getDescription();
}
// 具体组件
public class SimpleCoffee implements Coffee {
@Override
public BigDecimal getCost() { return new BigDecimal("4.00"); }
@Override
public String getDescription() { return "Simple coffee"; }
}
// 装饰器基类
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public BigDecimal getCost() {
return decoratedCoffee.getCost();
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// 具体装饰器
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public BigDecimal getCost() {
return super.getCost().add(new BigDecimal("0.5"));
}
@Override
public String getDescription() {
return super.getDescription() + ", with milk";
}
}
使用示例:
java复制Coffee myCoffee = new SimpleCoffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);
System.out.println(myCoffee.getDescription());
// 输出:Simple coffee, with milk, with sugar
System.out.println(myCoffee.getCost()); // 4.7
3.2 JDK中的装饰器实践
Java IO流是装饰器模式的教科书级实现:
java复制InputStream in = new FileInputStream("data.txt");
in = new BufferedInputStream(in); // 添加缓冲功能
in = new GZIPInputStream(in); // 添加解压功能
这种设计的精妙之处在于:
- 每个装饰器只关注自己的功能增强
- 可以任意组合装饰顺序
- 符合单一职责原则
3.3 装饰器与代理模式的区别
新手常混淆这两种模式,关键区别在于:
- 装饰器:增强对象功能
- 代理:控制对象访问
例如,Spring的AOP代理会在方法调用前后添加事务管理,这属于控制访问;而为IO流添加缓冲功能则属于功能增强。
4. 组合模式:树形结构处理专家
4.1 文件系统建模案例
开发文件浏览器时,需要处理文件和文件夹的统一操作。组合模式让我们能用一致的方式处理单个对象和组合对象:
java复制// 组件接口
public interface FileSystemComponent {
void display(String indent);
}
// 叶子节点
public class File implements FileSystemComponent {
private String name;
public File(String name) { this.name = name; }
@Override
public void display(String indent) {
System.out.println(indent + "📄 " + name);
}
}
// 组合节点
public class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name) { this.name = name; }
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void display(String indent) {
System.out.println(indent + "📁 " + name);
for (FileSystemComponent child : children) {
child.display(indent + " ");
}
}
}
使用示例:
java复制Directory root = new Directory("root");
root.add(new File("readme.txt"));
Directory src = new Directory("src");
src.add(new File("Main.java"));
root.add(src);
root.display("");
/* 输出:
📁 root
📄 readme.txt
📁 src
📄 Main.java
*/
4.2 组合模式的透明性与安全性
透明性实现(推荐):
- 所有方法定义在Component中
- 叶子节点对不支持的操作抛出UnsupportedOperationException
安全性实现:
- 只在Composite中定义管理子组件的方法
- 牺牲了透明性,客户端需要类型判断
经验:在Java集合框架中,
Component相当于Collection,Composite相当于AbstractCollection,而具体集合类如ArrayList就是叶子节点。
5. 外观模式:复杂系统的门面担当
5.1 电商下单流程简化
现代电商下单涉及多个子系统:库存、支付、物流、通知等。外观模式提供一个统一入口:
java复制public class OrderFacade {
private InventoryService inventory;
private PaymentService payment;
private ShippingService shipping;
private NotificationService notification;
public OrderFacade() {
this.inventory = new InventoryService();
this.payment = new PaymentService();
this.shipping = new ShippingService();
this.notification = new NotificationService();
}
public void placeOrder(Order order) {
if (!inventory.checkStock(order)) {
throw new RuntimeException("Out of stock");
}
payment.processPayment(order);
inventory.updateStock(order);
shipping.scheduleDelivery(order);
notification.sendEmail(order);
}
}
客户端调用变得极其简单:
java复制Order order = new Order("user123", List.of("item1", "item2"));
new OrderFacade().placeOrder(order);
5.2 外观模式与中介者模式的区别
两者都用于简化交互,但侧重点不同:
- 外观:为子系统提供简化接口
- 中介者:协调对象间的交互
例如,Spring的JdbcTemplate就是典型外观,它封装了JDBC的复杂操作;而事件总线则属于中介者。
6. 代理模式:对象的访问控制
6.1 虚拟代理实现图片懒加载
在图片浏览器中,加载大图需要时间,可以用虚拟代理先显示占位图:
java复制public interface Image {
void display();
}
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
// 模拟耗时操作
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
public class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
使用时代理对客户端透明:
java复制Image image = new ProxyImage("photo.jpg");
image.display(); // 第一次调用会加载图片
image.display(); // 直接使用已加载图片
6.2 动态代理进阶
Java的java.lang.reflect.Proxy可以实现运行时动态代理:
java复制public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
// 创建代理
UserService userService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LoggingHandler(new UserServiceImpl())
);
这种技术在Spring AOP中被广泛使用,实现了声明式事务、安全控制等功能。
7. 桥接模式与享元模式
7.1 桥接模式:抽象与实现分离
开发跨平台UI框架时,桥接模式能优雅地处理不同操作系统下的绘制:
java复制// 抽象部分
public abstract class Window {
protected WindowImpl impl;
public Window(WindowImpl impl) {
this.impl = impl;
}
public abstract void draw();
}
// 实现部分接口
public interface WindowImpl {
void drawLine(int x1, int y1, int x2, int y2);
void drawText(String text, int x, int y);
}
// 具体实现
public class WindowsWindowImpl implements WindowImpl {
@Override
public void drawLine(int x1, int y1, int x2, int y2) {
System.out.printf("Windows draw line (%d,%d)-(%d,%d)\n", x1,y1,x2,y2);
}
@Override
public void drawText(String text, int x, int y) {
System.out.printf("Windows draw text '%s' at (%d,%d)\n", text,x,y);
}
}
// 扩展抽象
public class IconWindow extends Window {
public IconWindow(WindowImpl impl) {
super(impl);
}
@Override
public void draw() {
impl.drawLine(0, 0, 100, 100);
impl.drawText("Icon", 50, 50);
}
}
这种设计允许窗口类型和平台实现独立变化,符合开闭原则。
7.2 享元模式:高效共享对象
在文字处理器中,字符对象可以共享:
java复制public class Character {
private char c;
private Font font; // 内部状态
private Color color; // 外部状态
public Character(char c, Font font) {
this.c = c;
this.font = font;
}
public void display(Color color) {
this.color = color;
System.out.printf("Display %c with %s and %s\n", c, font, color);
}
}
public class CharacterFactory {
private Map<Character, Map<Font, Character>> pool = new HashMap<>();
public Character getCharacter(char c, Font font) {
if (!pool.containsKey(c)) {
pool.put(c, new HashMap<>());
}
Map<Font, Character> fontMap = pool.get(c);
if (!fontMap.containsKey(font)) {
fontMap.put(font, new Character(c, font));
}
return fontMap.get(font);
}
}
使用享元模式后,相同字符和字体的组合只会创建一次,大幅减少内存占用。
8. 结构型模式综合应用实战
8.1 电商促销系统设计
假设我们要实现一个支持多种促销策略(满减、折扣、赠品)的电商系统,可以组合使用多种结构型模式:
- 策略模式+装饰器模式处理促销叠加:
java复制public interface Promotion {
BigDecimal apply(BigDecimal amount);
}
public class DiscountPromotion implements Promotion {
private BigDecimal rate;
public DiscountPromotion(BigDecimal rate) { this.rate = rate; }
@Override
public BigDecimal apply(BigDecimal amount) {
return amount.multiply(rate);
}
}
public class PromotionDecorator implements Promotion {
protected Promotion promotion;
public PromotionDecorator(Promotion promotion) {
this.promotion = promotion;
}
@Override
public BigDecimal apply(BigDecimal amount) {
return promotion.apply(amount);
}
}
public class GiftPromotion extends PromotionDecorator {
private String giftId;
public GiftPromotion(Promotion promotion, String giftId) {
super(promotion);
this.giftId = giftId;
}
@Override
public BigDecimal apply(BigDecimal amount) {
System.out.println("Add gift: " + giftId);
return super.apply(amount);
}
}
- 外观模式提供统一入口:
java复制public class OrderServiceFacade {
private InventoryService inventory;
private PromotionService promotion;
private PaymentService payment;
public BigDecimal checkout(Order order, List<Promotion> promotions) {
inventory.checkStock(order);
BigDecimal amount = order.getTotalAmount();
for (Promotion p : promotions) {
amount = p.apply(amount);
}
payment.process(amount);
return amount;
}
}
- 代理模式实现促销缓存:
java复制public class PromotionCacheProxy implements Promotion {
private PromotionService realService;
private Map<String, Promotion> cache = new HashMap<>();
@Override
public Promotion getPromotion(String id) {
if (cache.containsKey(id)) {
return cache.get(id);
}
Promotion p = realService.getPromotion(id);
cache.put(id, p);
return p;
}
}
8.2 性能优化实践
在实现结构型模式时,需要注意以下性能要点:
- 装饰器链长度:过多装饰层会导致调用栈过深,影响性能
- 享元对象清理:长期不用的享元对象应及时清除,防止内存泄漏
- 代理模式开销:动态代理会引入反射调用成本,高频场景需要权衡
我曾经在一个物流系统中过度使用装饰器模式(达到8层嵌套),导致方法调用性能下降30%。后来通过将部分装饰逻辑合并,优化到4层后性能恢复正常。
