结构型模式:组合模式

什么是组合模式

组合模式(Composite Pattern)是一种强大的结构型设计模式,它允许将对象组合成树形结构以表示"部分-整体"的层次关系。这种模式的精髓在于客户端可以统一处理单个对象和对象组合,无需区分它们之间的差异。

组合模式的核心思想是将单个对象(叶子节点)和组合对象(容器节点)一视同仁,从而使得客户端代码能够以相同的方式处理单个对象和对象的集合。这种设计实现了"多个对象组合成为一个对象"的无缝操作,极大地简化了处理复杂层次结构的代码。

在日常开发中,当我们面对树形结构数据时,组合模式往往能提供一种优雅的解决方案。无论是文件系统、菜单结构、公司组织架构,还是XML文档,组合模式都能发挥重要作用。

组合模式的结构

组合模式主要由以下几个角色组成:

  1. 组件(Component):定义组合中所有对象的共同接口,可以是抽象类或接口。它声明了叶子节点和组合节点共有的操作,有时也会实现所有类共有的默认行为。
  2. 叶子(Leaf):表示组合中的叶子节点对象,没有子节点。叶子节点实现组件接口,执行自身的具体操作。
  3. 组合(Composite):表示组合中的分支节点对象,包含子节点。组合节点实现组件接口,通常将操作委托给其子组件,并处理子组件的添加和删除。
  4. 客户端(Client):通过组件接口操作组合结构中的对象,无需区分是与叶子节点还是组合节点交互。

下面是组合模式的UML类图:

图片

组合模式的基本实现

下面是组合模式的基本Java实现:

// 组件接口
public abstract class Component {
    protected String name;
    
    public Component(String name) {
        this.name = name;
    }
    
    // 核心业务方法
    public abstract void operation();
    
    // 添加子节点方法(默认实现抛出异常)
    public void add(Component component) {
        throw new UnsupportedOperationException("不支持添加操作");
    }
    
    // 移除子节点方法(默认实现抛出异常)
    public void remove(Component component) {
        throw new UnsupportedOperationException("不支持移除操作");
    }
    
    // 获取子节点方法(默认实现抛出异常)
    public Component getChild(int index) {
        throw new UnsupportedOperationException("不支持获取子节点操作");
    }
    
    public String getName() {
        return name;
    }
}

// 叶子节点
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }
    
    @Override
    public void operation() {
        System.out.println("叶子节点 " + name + " 的操作");
    }
}

// 组合节点
public class Composite extends Component {
    private List<Component> children = new ArrayList<>();
    
    public Composite(String name) {
        super(name);
    }
    
    @Override
    public void operation() {
        System.out.println("组合节点 " + name + " 的操作");
        
        // 对所有子节点执行操作
        for (Component component : children) {
            component.operation();
        }
    }
    
    @Override
    public void add(Component component) {
        children.add(component);
    }
    
    @Override
    public void remove(Component component) {
        children.remove(component);
    }
    
    @Override
    public Component getChild(int index) {
        return children.get(index);
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建根节点
        Composite root = new Composite("根节点");
        
        // 创建并添加叶子节点
        root.add(new Leaf("叶子A"));
        root.add(new Leaf("叶子B"));
        
        // 创建并添加组合节点
        Composite composite = new Composite("组合X");
        composite.add(new Leaf("叶子XA"));
        composite.add(new Leaf("叶子XB"));
        
        // 将组合节点添加到根节点
        root.add(composite);
        
        // 创建并添加另一个叶子节点
        root.add(new Leaf("叶子C"));
        
        // 执行操作
        root.operation();
    }
}

实际应用示例:文件系统

以下是使用组合模式实现简单文件系统的例子:

// 文件系统组件接口
abstract class FileSystemComponent {
    protected String name;
    protected String path;
    
    public FileSystemComponent(String name, String path) {
        this.name = name;
        this.path = path;
    }
    
    // 显示详细信息
    public abstract void display(int depth);
    
    // 获取大小
    public abstract long getSize();
    
    // 辅助方法:根据深度添加缩进
    protected String getIndent(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("    ");
        }
        return sb.toString();
    }
    
    // 获取名称
    public String getName() {
        return name;
    }
    
    // 获取路径
    public String getPath() {
        return path;
    }
}

// 文件实现
class File extends FileSystemComponent {
    private long size;
    private String content;
    
    public File(String name, String path, long size) {
        super(name, path);
        this.size = size;
    }
    
    public File(String name, String path, String content) {
        super(name, path);
        this.content = content;
        this.size = content.length();
    }
    
    @Override
    public void display(int depth) {
        System.out.println(getIndent(depth) + "文件: " + name + " (" + size + " 字节)");
    }
    
    @Override
    public long getSize() {
        return size;
    }
    
    // 文件特有的操作
    public void setContent(String content) {
        this.content = content;
        this.size = content.length();
    }
    
    public String getContent() {
        return content;
    }
}

// 目录实现
class Directory extends FileSystemComponent {
    private List<FileSystemComponent> children = new ArrayList<>();
    
    public Directory(String name, String path) {
        super(name, path);
    }
    
    @Override
    public void display(int depth) {
        System.out.println(getIndent(depth) + "目录: " + name + " (" + getSize() + " 字节)");
        
        // 显示所有子组件
        for (FileSystemComponent component : children) {
            component.display(depth + 1);
        }
    }
    
    @Override
    public long getSize() {
        long totalSize = 0;
        
        // 累加所有子组件的大小
        for (FileSystemComponent component : children) {
            totalSize += component.getSize();
        }
        
        return totalSize;
    }
    
    // 目录特有的操作
    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 FileSystemComponent findByName(String name) {
        for (FileSystemComponent component : children) {
            if (component.getName().equals(name)) {
                return component;
            }
        }
        return null;
    }
}

// 文件系统示例
public class FileSystemExample {
    public static void main(String[] args) {
        // 创建根目录
        Directory root = new Directory("根目录", "/");
        
        // 创建子目录
        Directory docs = new Directory("文档", "/docs");
        Directory src = new Directory("源代码", "/src");
        
        // 向子目录添加文件
        docs.add(new File("readme.txt", "/docs/readme.txt", "这是一个README文件"));
        docs.add(new File("report.pdf", "/docs/report.pdf", 1024 * 1024)); // 1MB大小
        
        src.add(new File("Main.java", "/src/Main.java", "public class Main { public static void main(String[] args) { } }"));
        
        // 在src目录下创建子目录
        Directory util = new Directory("工具类", "/src/util");
        util.add(new File("Helper.java", "/src/util/Helper.java", 2048)); // 2KB大小
        src.add(util);
        
        // 向根目录添加子目录
        root.add(docs);
        root.add(src);
        root.add(new File("config.xml", "/config.xml", 512)); // 512字节大小
        
        // 显示整个文件系统
        System.out.println("===== 文件系统结构 =====");
        root.display(0);
        
        // 计算大小
        System.out.println("\n===== 大小统计 =====");
        System.out.println("根目录总大小: " + root.getSize() + " 字节");
        System.out.println("文档目录大小: " + docs.getSize() + " 字节");
        System.out.println("源代码目录大小: " + src.getSize() + " 字节");
        
        // 查找特定文件并获取内容
        Directory foundSrc = (Directory) root.findByName("源代码");
        File mainJava = (File) foundSrc.findByName("Main.java");
        System.out.println("\n===== 文件内容 =====");
        System.out.println(mainJava.getName() + " 的内容: " + mainJava.getContent());
    }
}

运行结果

===== 文件系统结构 =====
目录: 根目录 (1051719 字节)
    目录: 文档 (1048600 字节)
        文件: readme.txt (18 字节)
        文件: report.pdf (1048576 字节)
    目录: 源代码 (2107 字节)
        文件: Main.java (59 字节)
        目录: 工具类 (2048 字节)
            文件: Helper.java (2048 字节)
    文件: config.xml (512 字节)

===== 大小统计 =====
根目录总大小: 1051719 字节
文档目录大小: 1048600 字节
源代码目录大小: 2107 字节

===== 文件内容 =====
Main.java 的内容: public class Main { public static void main(String[] args) { } }

实际应用示例:公司组织结构

下面通过一个公司组织结构的例子进一步说明组合模式的实际应用:

// 组织结构组件接口
abstract class OrganizationComponent {
    protected String name;
    protected String position;
    
    public OrganizationComponent(String name, String position) {
        this.name = name;
        this.position = position;
    }
    
    // 显示组织结构
    public abstract void display(int depth);
    
    // 获取员工数量
    public abstract int getEmployeeCount();
    
    // 计算总薪资
    public abstract double getSalary();
    
    // 辅助方法:根据深度添加缩进
    protected String getIndent(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("    ");
        }
        return sb.toString();
    }
    
    public String getName() {
        return name;
    }
    
    public String getPosition() {
        return position;
    }
}

// 员工实现(叶子节点)
class Employee extends OrganizationComponent {
    private double salary;
    
    public Employee(String name, String position, double salary) {
        super(name, position);
        this.salary = salary;
    }
    
    @Override
    public void display(int depth) {
        System.out.println(getIndent(depth) + "员工: " + name + ", 职位: " + position + ", 薪资: " + salary);
    }
    
    @Override
    public int getEmployeeCount() {
        return 1;
    }
    
    @Override
    public double getSalary() {
        return salary;
    }
}

// 部门实现(组合节点)
class Department extends OrganizationComponent {
    private List<OrganizationComponent> subordinates = new ArrayList<>();
    
    public Department(String name, String position) {
        super(name, position);
    }
    
    @Override
    public void display(int depth) {
        System.out.println(getIndent(depth) + "部门: " + name + ", 主管: " + position + 
                ", 员工数: " + getEmployeeCount() + ", 总薪资: " + getSalary());
        
        // 显示所有下属
        for (OrganizationComponent component : subordinates) {
            component.display(depth + 1);
        }
    }
    
    @Override
    public int getEmployeeCount() {
        int count = 0;
        
        // 累加所有下属的员工数
        for (OrganizationComponent component : subordinates) {
            count += component.getEmployeeCount();
        }
        
        return count;
    }
    
    @Override
    public double getSalary() {
        double totalSalary = 0;
        
        // 累加所有下属的薪资
        for (OrganizationComponent component : subordinates) {
            totalSalary += component.getSalary();
        }
        
        return totalSalary;
    }
    
    // 添加下属
    public void add(OrganizationComponent component) {
        subordinates.add(component);
    }
    
    // 移除下属
    public void remove(OrganizationComponent component) {
        subordinates.remove(component);
    }
    
    // 获取特定下属
    public OrganizationComponent getChild(int index) {
        return subordinates.get(index);
    }
}

// 组织结构示例
public class OrganizationExample {
    public static void main(String[] args) {
        // 创建公司架构
        Department company = new Department("某科技公司", "CEO张三");
        
        // 创建部门
        Department rd = new Department("研发部", "研发总监李四");
        Department sales = new Department("销售部", "销售总监王五");
        
        // 向研发部添加子部门和员工
        Department frontend = new Department("前端组", "前端主管赵六");
        Department backend = new Department("后端组", "后端主管钱七");
        
        frontend.add(new Employee("小明", "前端工程师", 12000));
        frontend.add(new Employee("小红", "UI设计师", 13000));
        
        backend.add(new Employee("小刚", "Java工程师", 15000));
        backend.add(new Employee("小丽", "数据库工程师", 16000));
        backend.add(new Employee("小强", "DevOps工程师", 17000));
        
        rd.add(frontend);
        rd.add(backend);
        rd.add(new Employee("小张", "产品经理", 18000));
        
        // 向销售部添加员工
        sales.add(new Employee("大壮", "销售经理", 12000));
        sales.add(new Employee("大力", "销售代表", 8000));
        sales.add(new Employee("大勇", "销售代表", 8000));
        
        // 构建完整结构
        company.add(rd);
        company.add(sales);
        company.add(new Employee("大海", "财务总监", 20000));
        
        // 显示组织结构
        System.out.println("===== 组织结构图 =====");
        company.display(0);
        
        // 统计信息
        System.out.println("\n===== 统计信息 =====");
        System.out.println("公司总人数: " + company.getEmployeeCount() + " 人");
        System.out.println("公司总薪资: " + company.getSalary() + " 元");
        System.out.println("研发部人数: " + rd.getEmployeeCount() + " 人");
        System.out.println("研发部总薪资: " + rd.getSalary() + " 元");
        System.out.println("销售部人数: " + sales.getEmployeeCount() + " 人");
        System.out.println("销售部总薪资: " + sales.getSalary() + " 元");
        System.out.println("前端组人数: " + frontend.getEmployeeCount() + " 人");
        System.out.println("前端组总薪资: " + frontend.getSalary() + " 元");
        System.out.println("后端组人数: " + backend.getEmployeeCount() + " 人");
        System.out.println("后端组总薪资: " + backend.getSalary() + " 元");
    }
}

组合模式在Java中的应用

Java API中有多个使用组合模式的例子:

  1. javax.swing.JComponent:Swing UI组件的基类,包含叶子节点(如JButton、JTextField)和组合节点(如JPanel、JFrame)。

  2. java.awt.Container:AWT容器类,可以包含其他组件,形成组件树。

  3. java.io.File:可以表示文件或目录,目录可以包含其他文件或目录。

  4. org.w3c.dom.Node:XML DOM中的节点接口,节点可以有子节点。

组合模式的使用场景

组合模式在以下情况特别有用:

  1. 表示对象的部分-整体层次结构:当你需要表示树形结构时
  2. 希望客户端忽略组合对象与单个对象的差异:统一处理复杂和简单元素
  3. 需要递归组合:对象的组合是递归的
  4. 需要应用相同操作到所有组合元素:如统计、遍历
  5. 系统中存在树形结构:如文件系统、组织结构、GUI组件树等
  6. 需要动态组合和分解对象:对象组合可以在运行时变化

组合模式的变体

透明组合模式

在透明组合模式中,Component接口包含管理子节点的方法,让所有组件看起来是一样的:

// 透明组合模式中的组件接口
public interface TransparentComponent {
    void operation();
    void add(TransparentComponent component);
    void remove(TransparentComponent component);
    TransparentComponent getChild(int index);
}

// 叶子实现
public class TransparentLeaf implements TransparentComponent {
    private String name;
    
    public TransparentLeaf(String name) {
        this.name = name;
    }
    
    @Override
    public void operation() {
        System.out.println("叶子 " + name + " 的操作");
    }
    
    @Override
    public void add(TransparentComponent component) {
        throw new UnsupportedOperationException("叶子节点不支持添加操作");
    }
    
    @Override
    public void remove(TransparentComponent component) {
        throw new UnsupportedOperationException("叶子节点不支持移除操作");
    }
    
    @Override
    public TransparentComponent getChild(int index) {
        throw new UnsupportedOperationException("叶子节点不支持获取子节点操作");
    }
}

// 组合实现
public class TransparentComposite implements TransparentComponent {
    private String name;
    private List<TransparentComponent> children = new ArrayList<>();
    
    public TransparentComposite(String name) {
        this.name = name;
    }
    
    @Override
    public void operation() {
        System.out.println("组合 " + name + " 的操作");
        
        for (TransparentComponent component : children) {
            component.operation();
        }
    }
    
    @Override
    public void add(TransparentComponent component) {
        children.add(component);
    }
    
    @Override
    public void remove(TransparentComponent component) {
        children.remove(component);
    }
    
    @Override
    public TransparentComponent getChild(int index) {
        return children.get(index);
    }
}

安全组合模式

在安全组合模式中,Component接口只包含共同操作,管理子节点的方法只在Composite中定义:

// 安全组合模式中的组件接口
public interface SafeComponent {
    void operation();
}

// 叶子实现
public class SafeLeaf implements SafeComponent {
    private String name;
    
    public SafeLeaf(String name) {
        this.name = name;
    }
    
    @Override
    public void operation() {
        System.out.println("叶子 " + name + " 的操作");
    }
}

// 组合实现
public class SafeComposite implements SafeComponent {
    private String name;
    private List<SafeComponent> children = new ArrayList<>();
    
    public SafeComposite(String name) {
        this.name = name;
    }
    
    @Override
    public void operation() {
        System.out.println("组合 " + name + " 的操作");
        
        for (SafeComponent component : children) {
            component.operation();
        }
    }
    
    // 组合特有的方法
    public void add(SafeComponent component) {
        children.add(component);
    }
    
    public void remove(SafeComponent component) {
        children.remove(component);
    }
    
    public SafeComponent getChild(int index) {
        return children.get(index);
    }
}

组合模式与其他模式的比较

组合模式 vs 装饰器模式

  • 组合模式:创建对象的树形结构,重点在于"包含"关系
  • 装饰器模式:动态添加对象的职责,重点在于"添加功能"

组合模式 vs 享元模式

  • 组合模式:关注对象的组合结构
  • 享元模式:关注大量相似对象的共享,减少内存使用

组合模式 vs 迭代器模式

  • 组合模式:定义树形结构
  • 迭代器模式:遍历这些结构(两者经常一起使用)

组合模式的优点

  1. 统一处理:可以一致地处理单个对象和组合对象
  2. 开闭原则:可以自由添加新种类的组件,而不修改现有代码
  3. 简化客户端代码:客户端不需要知道处理的是叶子还是组合
  4. 表达能力强:能够表达复杂的树形结构
  5. 递归结构:可以方便地对整个层次结构执行操作

组合模式的缺点

  1. 接口过于一般化:难以限制组合中的组件
  2. 过于简化:有时难以区分组合对象和叶子对象的行为差异
  3. 可能导致设计过于通用:对特例的处理可能不够高效
  4. 性能考虑:深层次的递归操作可能带来性能问题

组合模式的最佳实践

  1. 清晰定义组件接口:明确组件的公共操作
  2. 考虑安全性与透明性:在透明性和类型安全性之间做出平衡
  3. 注意组合关系:考虑组件之间的从属关系
  4. 合理设计递归操作:操作时注意终止条件和性能
  5. 区分共有行为和特有行为:明确哪些行为适用于所有组件,哪些只适用于特定类型
  6. 缓存计算结果:如果递归计算开销大,考虑缓存结果
  7. 考虑父节点引用:某些情况下,子节点需要引用父节点

高级实现:具有父节点引用的组合模式

对于某些应用,子节点可能需要知道其父节点:

// 具有父节点引用的组件
abstract class ComponentWithParent {
    protected String name;
    protected ComponentWithParent parent;
    
    public ComponentWithParent(String name) {
        this.name = name;
    }
    
    public abstract void operation();
    
    public void setParent(ComponentWithParent parent) {
        this.parent = parent;
    }
    
    public ComponentWithParent getParent() {
        return parent;
    }
    
    // 获取完整路径
    public String getPath() {
        if (parent == null) {
            return "/" + name;
        } else {
            return parent.getPath() + "/" + name;
        }
    }
}

// 带父节点引用的叶子
class LeafWithParent extends ComponentWithParent {
    public LeafWithParent(String name) {
        super(name);
    }
    
    @Override
    public void operation() {
        System.out.println("叶子 " + name + " 的操作,路径: " + getPath());
    }
}

// 带父节点引用的组合
class CompositeWithParent extends ComponentWithParent {
    private List<ComponentWithParent> children = new ArrayList<>();
    
    public CompositeWithParent(String name) {
        super(name);
    }
    
    @Override
    public void operation() {
        System.out.println("组合 " + name + " 的操作,路径: " + getPath());
        
        for (ComponentWithParent component : children) {
            component.operation();
        }
    }
    
    public void add(ComponentWithParent component) {
        children.add(component);
        component.setParent(this);
    }
    
    public void remove(ComponentWithParent component) {
        children.remove(component);
        component.setParent(null);
    }
    
    public ComponentWithParent getChild(int index) {
        return children.get(index);
    }
}

实际项目中的应用:XML/HTML解析器

在实际项目中,组合模式常用于文档对象模型(DOM)的实现:

// DOM节点组件
interface DOMNode {
    void render();
    String getContent();
}

// 文本节点(叶子)
class TextNode implements DOMNode {
    private String text;
    
    public TextNode(String text) {
        this.text = text;
    }
    
    @Override
    public void render() {
        System.out.print(text);
    }
    
    @Override
    public String getContent() {
        return text;
    }
}

// 元素节点(组合)
class ElementNode implements DOMNode {
    private String tagName;
    private Map<String, String> attributes = new HashMap<>();
    private List<DOMNode> children = new ArrayList<>();
    
    public ElementNode(String tagName) {
        this.tagName = tagName;
    }
    
    public void setAttribute(String name, String value) {
        attributes.put(name, value);
    }
    
    public void addChild(DOMNode node) {
        children.add(node);
    }
    
    @Override
    public void render() {
        System.out.print("<" + tagName);
        
        // 渲染属性
        for (Map.Entry<String, String> attr : attributes.entrySet()) {
            System.out.print(" " + attr.getKey() + "=\"" + attr.getValue() + "\"");
        }
        
        if (children.isEmpty()) {
            System.out.print("/>");
            return;
        }
        
        System.out.print(">");
        
        // 渲染子节点
        for (DOMNode child : children) {
            child.render();
        }
        
        System.out.print("</" + tagName + ">");
    }
    
    @Override
    public String getContent() {
        StringBuilder content = new StringBuilder();
        for (DOMNode child : children) {
            content.append(child.getContent());
        }
        return content.toString();
    }
}

// DOM解析示例
public class DOMExample {
    public static void main(String[] args) {
        // 创建HTML文档
        ElementNode html = new ElementNode("html");
        
        // 创建head部分
        ElementNode head = new ElementNode("head");
        ElementNode title = new ElementNode("title");
        title.addChild(new TextNode("组合模式示例"));
        head.addChild(title);
        
        // 创建body部分
        ElementNode body = new ElementNode("body");
        
        // 添加h1标题
        ElementNode h1 = new ElementNode("h1");
        h1.addChild(new TextNode("这是一个DOM节点树"));
        body.addChild(h1);
        
        // 添加段落
        ElementNode p = new ElementNode("p");
        p.addChild(new TextNode("使用组合模式实现简单的DOM结构。"));
        p.setAttribute("class", "description");
        body.addChild(p);
        
        // 添加列表
        ElementNode ul = new ElementNode("ul");
        ul.setAttribute("id", "features");
        
        ElementNode li1 = new ElementNode("li");
        li1.addChild(new TextNode("统一的节点接口"));
        
        ElementNode li2 = new ElementNode("li");
        li2.addChild(new TextNode("叶子和组合的一致处理"));
        
        ElementNode li3 = new ElementNode("li");
        li3.addChild(new TextNode("递归组合结构"));
        
        ul.addChild(li1);
        ul.addChild(li2);
        ul.addChild(li3);
        
        body.addChild(ul);
        
        // 组合完整的HTML文档
        html.addChild(head);
        html.addChild(body);
        
        // 渲染HTML
        System.out.println("===== DOM渲染结果 =====");
        html.render();
        
        // 获取纯文本内容
        System.out.println("\n\n===== 文档的文本内容 =====");
        System.out.println(html.getContent());
    }
}

总结

组合模式是一种强大的结构型设计模式,它允许你将对象组合成树形结构,并能以一致的方式处理单个对象和对象组合。这种模式特别适合表示"部分-整体"层次结构,使得客户端代码能够统一处理简单和复杂的元素。

通过组合模式,我们可以:

  1. 构建复杂的树形对象结构
  2. 使客户端能够以统一方式处理所有对象
  3. 方便地对整个结构应用操作
  4. 动态地添加或删除组件

在实际应用中,组合模式广泛用于文件系统、组织结构、GUI组件、菜单系统、XML/HTML解析等领域。

在设计组合模式时,需要在透明性和安全性之间做出权衡,同时还需要考虑递归操作的效率和组件之间的关系管理。恰当地使用组合模式可以大大简化对复杂层次结构的处理,使代码更加优雅和易于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck_ff0810

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值