《设计模式》第九篇:三大类型之结构型模式

本期内容为自己总结归档,共分十一章,本人遇到过的面试问题会重点标记。

《设计模式》第一篇:初识

《设计模式》第二篇:单例模式

《设计模式》第三篇:工厂模式

《设计模式》第四篇:观察模式

《设计模式》第五篇:策略模式

《设计模式》第六篇:装饰器模式

《设计模式》第七篇:适配器模式

《设计模式》第八篇:创建型模式

《设计模式》第九篇:结构型模式

《设计模式》第十篇:行为型模式

《设计模式》第十一篇:总结&常用案例

(若有任何疑问,可在评论区告诉我,看到就回复)

一、结构型模式全景

1.1 什么是结构型模式?

结构型模式关注类和对象的组合方式,通过组合不同的类和对象形成更大的结构,同时保持结构的灵活和高效。这些模式描述如何将类或对象结合在一起形成更大的结构,就像使用不同的建筑材料构建复杂的建筑结构。

1.2 结构型模式的分类

结构型模式包括七种模式:适配器模式(Adapter)、桥接模式(Bridge)、组合模式(Composite)、装饰器模式(Decorator)、外观模式(Facade)、享元模式(Flyweight)和代理模式(Proxy)。它们可以分为两类:

类结构型模式 :通过继承来组合接口或实现
对象结构型模式:通过组合对象来形成更大的结构

1.3 结构型模式的共同目标

所有结构型模式都致力于解决以下问题:

  1. 解耦:减少组件间的依赖

  2. 复用:提高代码的可复用性

  3. 灵活性:使系统更容易扩展和修改

  4. 清晰性:使系统结构更加清晰易懂

二、组合模式(Composite Pattern)

2.1 组合模式的定义

组合模式允许你将对象组合成树形结构来表示"部分-整体"的层次结构,使得客户端可以统一地对待单个对象和组合对象。

2.2 组合模式的核心思想

组合模式通过定义包含基本对象和组合对象的类层次结构,让客户端可以一致地使用组合结构和单个对象。

2.3 组合模式实现:文件系统

java 复制代码
// 组件接口
public interface FileSystemComponent {
    void display(int indent);
    long getSize();
    String getName();
}

// 叶子节点:文件
public class File implements FileSystemComponent {
    private String name;
    private long size;
    
    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }
    
    @Override
    public void display(int indent) {
        String spaces = " ".repeat(indent);
        System.out.println(spaces + "📄 " + name + " (" + size + " bytes)");
    }
    
    @Override
    public long getSize() {
        return size;
    }
    
    @Override
    public String getName() {
        return name;
    }
}

// 组合节点:目录
public class Directory implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> children;
    
    public Directory(String name) {
        this.name = name;
        this.children = new ArrayList<>();
    }
    
    @Override
    public void display(int indent) {
        String spaces = " ".repeat(indent);
        System.out.println(spaces + "- " + name);
        
        // 显示所有子组件
        for (FileSystemComponent child : children) {
            child.display(indent + 2);
        }
    }
    
    @Override
    public long getSize() {
        long totalSize = 0;
        for (FileSystemComponent child : children) {
            totalSize += child.getSize();
        }
        return totalSize;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    // 管理子组件的方法
    public void add(FileSystemComponent component) {
        children.add(component);
    }
    
    public void remove(FileSystemComponent component) {
        children.remove(component);
    }
    
    public FileSystemComponent getChild(int index) {
        return children.get(index);
    }
    
    public List<FileSystemComponent> getChildren() {
        return new ArrayList<>(children);
    }
}

// 客户端使用
public class CompositePatternDemo {
    public static void main(String[] args) {
        System.out.println("=== 组合模式示例:文件系统 ===");
        
        // 创建文件
        FileSystemComponent file1 = new File("document.txt", 1024);
        FileSystemComponent file2 = new File("image.jpg", 2048);
        FileSystemComponent file3 = new File("video.mp4", 10240);
        
        // 创建子目录
        Directory subDir = new Directory("Documents");
        subDir.add(new File("resume.pdf", 512));
        subDir.add(new File("contract.docx", 768));
        
        // 创建主目录
        Directory rootDir = new Directory("Root");
        rootDir.add(file1);
        rootDir.add(file2);
        
        // 创建另一个目录
        Directory mediaDir = new Directory("Media");
        mediaDir.add(file3);
        mediaDir.add(new File("music.mp3", 3072));
        
        // 将子目录添加到主目录
        rootDir.add(subDir);
        rootDir.add(mediaDir);
        
        // 显示整个结构
        System.out.println("\n文件系统结构:");
        rootDir.display(0);
        
        // 计算总大小
        System.out.println("\n总大小: " + rootDir.getSize() + " bytes");
        
        // 统一处理:对文件和目录执行相同操作
        System.out.println("\n遍历所有组件:");
        traverse(rootDir, 0);
    }
    
    private static void traverse(FileSystemComponent component, int depth) {
        component.display(depth);
        
        // 如果是目录,递归遍历子组件
        if (component instanceof Directory) {
            Directory dir = (Directory) component;
            for (FileSystemComponent child : dir.getChildren()) {
                traverse(child, depth + 1);
            }
        }
    }
}

3.4 组合模式的特点

优点

  1. 简化客户端代码:客户端可以一致地处理简单和复杂元素

  2. 容易添加新组件:新增组件类型不影响现有结构

  3. 灵活的结构:可以方便地构建复杂的树形结构

  4. 符合开闭原则:对扩展开放,对修改封闭

缺点

  1. 设计复杂:需要仔细设计组件接口

  2. 类型安全检查:需要运行时类型检查

  3. 可能违反单一职责:组件接口可能过于通用

适用场景

  1. 表示对象的"部分-整体"层次结构

  2. 希望客户端忽略组合对象与单个对象的差异

  3. 树形菜单、文件系统等层次结构

  4. GUI组件树(如Swing/JavaFX)

三、代理模式(Proxy Pattern)

3.1 代理模式的定义

代理模式为其他对象提供一种代理以控制对这个对象的访问。代理模式在客户端和目标对象之间起到中介作用,可以用于控制访问、延迟加载、记录日志等。

3.2 代理模式的核心思想

代理模式通过创建一个代理对象来控制对原始对象的访问,可以在不改变原始对象的情况下增加额外的功能。

3.3 代理模式示例:图片加载器

java 复制代码
// 主题接口:图片
public interface Image {
    void display();
    String getFileName();
}

// 真实主题:高分辨率图片
public class HighResolutionImage implements Image {
    private String fileName;
    private byte[] imageData;
    
    public HighResolutionImage(String fileName) {
        this.fileName = fileName;
        loadImageFromDisk(); // 昂贵的加载操作
    }
    
    private void loadImageFromDisk() {
        System.out.println("从磁盘加载高分辨率图片: " + fileName);
        // 模拟耗时加载过程
        try {
            Thread.sleep(2000); // 2秒加载时间
            imageData = new byte[1024 * 1024]; // 1MB 数据
            System.out.println("图片加载完成: " + fileName);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    @Override
    public void display() {
        System.out.println("显示图片: " + fileName + " (大小: " + 
                          (imageData != null ? imageData.length : 0) + " bytes)");
    }
    
    @Override
    public String getFileName() {
        return fileName;
    }
}

// 代理:图片代理(虚拟代理)
public class ImageProxy implements Image {
    private String fileName;
    private HighResolutionImage realImage;
    private boolean loaded;
    
    public ImageProxy(String fileName) {
        this.fileName = fileName;
        this.loaded = false;
        System.out.println("创建图片代理: " + fileName);
    }
    
    @Override
    public void display() {
        // 延迟加载:只有在真正需要时才加载图片
        if (!loaded) {
            loadRealImage();
        }
        realImage.display();
    }
    
    private void loadRealImage() {
        if (realImage == null) {
            realImage = new HighResolutionImage(fileName);
        }
        loaded = true;
    }
    
    @Override
    public String getFileName() {
        return fileName;
    }
    
    // 代理的其他功能
    public void showPlaceholder() {
        System.out.println("显示占位符: " + fileName + " (图片未加载)");
    }
    
    public boolean isLoaded() {
        return loaded;
    }
}

// 保护代理:访问控制
public interface SensitiveDocument {
    void viewDocument();
    void editDocument();
}

public class RealDocument implements SensitiveDocument {
    private String content;
    private String accessLevel;
    
    public RealDocument(String content, String accessLevel) {
        this.content = content;
        this.accessLevel = accessLevel;
    }
    
    @Override
    public void viewDocument() {
        System.out.println("查看文档内容: " + content);
    }
    
    @Override
    public void editDocument() {
        System.out.println("编辑文档内容");
        content = "已编辑的内容";
    }
}

public class DocumentProxy implements SensitiveDocument {
    private RealDocument realDocument;
    private String userAccessLevel;
    
    public DocumentProxy(String content, String documentAccessLevel, 
                        String userAccessLevel) {
        this.realDocument = new RealDocument(content, documentAccessLevel);
        this.userAccessLevel = userAccessLevel;
    }
    
    @Override
    public void viewDocument() {
        // 检查访问权限
        if (hasAccess("VIEW")) {
            realDocument.viewDocument();
        } else {
            System.out.println("权限不足:无法查看文档");
        }
    }
    
    @Override
    public void editDocument() {
        // 检查访问权限
        if (hasAccess("EDIT")) {
            realDocument.editDocument();
        } else {
            System.out.println("权限不足:无法编辑文档");
        }
    }
    
    private boolean hasAccess(String operation) {
        // 简单的权限检查逻辑
        if ("ADMIN".equals(userAccessLevel)) {
            return true;
        } else if ("EDITOR".equals(userAccessLevel)) {
            return "VIEW".equals(operation) || "EDIT".equals(operation);
        } else if ("VIEWER".equals(userAccessLevel)) {
            return "VIEW".equals(operation);
        }
        return false;
    }
}

// 客户端使用
public class ProxyPatternDemo {
    public static void main(String[] args) {
        System.out.println("=== 代理模式示例 ===");
        
        System.out.println("\n1. 虚拟代理示例(图片延迟加载):");
        Image image1 = new ImageProxy("photo1.jpg");
        Image image2 = new ImageProxy("photo2.jpg");
        
        // 创建代理时不会立即加载图片
        System.out.println("图片代理已创建,但未加载真实图片");
        
        // 只有在显示时才加载图片
        image1.display(); // 第一次显示,会加载图片
        image1.display(); // 第二次显示,使用已加载的图片
        
        System.out.println("\n2. 保护代理示例(访问控制):");
        SensitiveDocument confidentialDoc = new DocumentProxy(
            "机密内容", "CONFIDENTIAL", "VIEWER"
        );
        
        confidentialDoc.viewDocument(); // 可以查看
        confidentialDoc.editDocument(); // 权限不足
        
        System.out.println("\n提升权限到 ADMIN:");
        SensitiveDocument adminDoc = new DocumentProxy(
            "机密内容", "CONFIDENTIAL", "ADMIN"
        );
        adminDoc.viewDocument();
        adminDoc.editDocument();
    }
}

3.4 代理模式的类型

代理类型 目的 示例
虚拟代理 延迟加载 图片代理,直到需要时才加载大图片
保护代理 访问控制 权限检查,控制对敏感对象的访问
远程代理 远程访问 RMI,访问远程对象
智能引用 额外功能 引用计数,对象锁定时自动加载

3.5 代理模式的特点

优点

  1. 控制对象访问:可以在访问对象前后添加额外操作

  2. 开闭原则:不修改原始对象即可添加新功能

  3. 延迟加载:可以延迟昂贵对象的创建

  4. 职责清晰:代理对象专注于控制访问,真实对象专注于业务

缺点

  1. 增加系统复杂度:引入额外的代理层

  2. 可能降低性能:代理调用可能增加额外开销

  3. 可能过度设计:简单场景不需要代理

适用场景

  1. 需要控制对对象的访问

  2. 需要延迟加载大型对象

  3. 需要为对象添加额外功能(如日志、缓存)

  4. 需要保护对象不被直接访问

四、总结

4.1 各模式对比表格

模式 主要目的 关键思想 适用场景
适配器 接口转换 包装对象,提供不同接口 集成不兼容接口
桥接 分离抽象与实现 组合替代继承 多维度变化的系统
组合 树形结构 部分-整体统一处理 层次结构对象
装饰器 动态添加功能 包装对象,增强功能 运行时功能扩展
外观 简化接口 提供统一入口 复杂子系统简化
享元 对象共享 分离内部/外部状态 大量相似对象
代理 控制访问 中介控制对象访问 访问控制、延迟加载
相关推荐
努力d小白1 小时前
leetcode438.找到字符串中所有字母异位词
java·javascript·算法
没有bug.的程序员2 小时前
RocketMQ 与 Kafka 深度对垒:分布式消息引擎内核、事务金融级实战与高可用演进指南
java·分布式·kafka·rocketmq·分布式消息·引擎内核·事务金融
jiang_changsheng2 小时前
工作流agent汇总分析 2
java·人工智能·git·python·机器学习·github·语音识别
树码小子2 小时前
SpringIoC & DI (4)DI详解(三种注入方式)
java·后端·spring·ioc
落羽的落羽2 小时前
【Linux系统】从零实现一个简易的shell!
android·java·linux·服务器·c++·人工智能·机器学习
1104.北光c°2 小时前
【黑马点评项目笔记 | 优惠券秒杀篇】构建高并发秒杀系统
java·开发语言·数据库·redis·笔记·spring·nosql
ruleslol2 小时前
普通流(Stream<T>)和原始类型特化流(IntStream, LongStream, DoubleStream)的区别
java
隐退山林2 小时前
JavaEE初阶:文件操作和IO
java·java-ee
2501_907136822 小时前
PDF增效工具 Quite imposing plus6
java·开发语言