• 设计模式---组合模式


    简述

    • 类型:结构型
    • 目的:将对象集合组合成树形结构,使客户端可以以一致的方式处理单个对象(叶子节点)组合对象(根节点)

    话不多说,上优化案例。

    优化案例

    最初版v0

    不使用组合模式。
    现有一个文件和目录的管理模块。如样例。

    public class File { // 文件类
    private String path;
    private Directory parent;
    public File(Directory dir, String path) {
    if (dir == null)
    throw new RuntimeException("输入的dir不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.parent = dir;
    this.path = dir.getPath() + path;
    dir.add(this);
    }
    public String getPath() {
    return this.path;
    }
    }
    public class Directory { // 目录类
    private String path;
    private List dirs = new ArrayList<>();
    private List files = new ArrayList<>();
    public Directory(String path) {
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = path;
    }
    public Directory(Directory parent, String path) {
    if (parent == null)
    throw new RuntimeException("输入的parent不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = parent.getPath() + path;
    parent.add(this);
    }
    public boolean add(File target) {
    for (File file : files)
    // 不能创建同名文件
    if (target.getPath().equals(file.getPath())) return false;
    files.add(target);
    return true;
    }
    public boolean add(Directory target) {
    for (Directory dir : dirs)
    // 不能创建同名目录
    if (target.getPath().equals(dir.getPath())) return false;
    dirs.add(target);
    return true;
    }
    public boolean remove(Directory target) {
    for (Directory dir : dirs)
    if (target.getPath().equals(dir.getPath())) {
    dirs.remove(dir);
    return true;
    }
    return false;
    }
    public boolean remove(File target) {
    for (File file : files)
    if (target.getPath().equals(file.getPath())) {
    files.remove(file);
    return true;
    }
    return false;
    }
    public String getPath() {
    return this.path;
    }
    public List getDirs() {
    return this.dirs;
    }
    public List getFiles() {
    return this.files;
    }
    }

    不使用组合模式,我们来看看客户端的使用。

    public class Client { // 客户端
    public static void main(String[] args) {
    // 创建各级目录
    Directory root = new Directory("/root");
    Directory home = new Directory(root, "/home");
    Directory user1 = new Directory(home, "/user1");
    Directory text = new Directory(user1, "/text");
    Directory image = new Directory(user1, "/image");
    Directory png = new Directory(image, "/png");
    Directory gif = new Directory(image, "/gif");
    // 创建文件
    File f1 = new File(text, "/f1.txt");
    File f2 = new File(text, "/f2.txt");
    File f3 = new File(png, "/f3.png");
    File f4 = new File(gif, "/f4.gif");
    File f5 = new File(png, "/f5.png");
    // 输出root下的文件或者目录路径
    print(root);
    }
    // 前序遍历目录下路径
    public static void print(Directory root) {
    System.out.println(root.getPath());
    List dirs = root.getDirs();
    List files = root.getFiles();
    for (int i = 0; i < dirs.size(); i ++) {
    print(dirs.get(i));
    }
    for (int i = 0; i < files.size(); i ++) {
    System.out.println(files.get(i).getPath());
    }
    }
    }

    可以看到print方法的实现比较复杂,因为FileDirectory是完全不同类型,所以只能对其分别处理。

    如何让客户端对于FileDirectory采用一致的处理方式?用组合模式啊!!!

    修改版v1(透明组合模式)

    public interface Node { // 从File和Directory中抽象出Node类
    boolean add(Node node);
    boolean remove(Node node);
    List getChildren();
    String getPath();
    }
    public class File implements Node {
    private String path;
    private Node parent;
    public File(Node parent, String path) {
    if (parent == null)
    throw new RuntimeException("输入的dir不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.parent = parent;
    this.path = parent.getPath() + path;
    parent.add(this);
    }
    public boolean add(Node node) { // 因为不是容器,所以重写这个方法无意义
    throw new RuntimeException("不支持此方法!");
    }
    public boolean remove(Node node) { // 同上
    throw new RuntimeException("不支持此方法!");
    }
    public List getChildren() { // 同上
    throw new RuntimeException("不支持此方法!");
    }
    public String getPath() {
    return this.path;
    }
    }
    public class Directory implements Node {
    private String path;
    private List children = new ArrayList<>();
    public Directory(String path) {
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = path;
    }
    public Directory(Node parent, String path) {
    if (parent == null)
    throw new RuntimeException("输入的parent不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = parent.getPath() + path;
    parent.add(this);
    }
    public boolean add(Node target) {
    for (Node node : children)
    // 不能创建同名文件
    if (target.getPath().equals(node.getPath())) return false;
    children.add(target);
    return true;
    }
    public boolean remove(Node target) {
    for (Node node : children)
    if (target.getPath().equals(node.getPath())) {
    children.remove(node);
    return true;
    }
    return false;
    }
    public String getPath() {
    return this.path;
    }
    public List getChildren() {
    return this.children;
    }
    }

    通过在FileDirectory的高层新增Node接口,面向接口编程加上FileDirectory形成的树形结构使得客户端可以很自然地一致处理FileDirectory。来看看客户端代码。

    public class Client {
    public static void main(String[] args) {
    // 创建各级目录
    Node root = new Directory("/root");
    Node home = new Directory(root, "/home");
    Node user1 = new Directory(home, "/user1");
    Node text = new Directory(user1, "/text");
    Node image = new Directory(user1, "/image");
    Node png = new Directory(image, "/png");
    Node gif = new Directory(image, "/gif");
    // 创建文件
    Node f1 = new File(text, "/f1.txt");
    Node f2 = new File(text, "/f2.txt");
    Node f3 = new File(png, "/f3.png");
    Node f4 = new File(gif, "/f4.gif");
    Node f5 = new File(png, "/f5.png");
    // 输出root下的文件或者目录路径
    print(root);
    }
    public static void print(Node root) {
    System.out.println(root.getPath());
    List nodes = root.getChildren();
    for (int i = 0; i < nodes.size(); i ++) {
    Node node = nodes.get(i);
    if (node instanceof File) {
    System.out.println(node.getPath());
    continue;
    }
    print(node);
    }
    }
    }

    别高兴的太早了,虽然我们实现了最初的需求,但是有一处的代码不是很健康。在File中有三个方法实际上并没有被实现,有些臃肿。

    修改版v2(安全组合模式)

    public interface Node { // 从File和Directory中抽象出Node类
    String getPath(); // 删除累赘的方法
    }
    public class File implements Node {
    private String path;
    private Node parent;
    public File(Directory parent, String path) {
    if (parent == null)
    throw new RuntimeException("输入的dir不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.parent = parent;
    this.path = parent.getPath() + path;
    parent.add(this);
    }
    public String getPath() {
    return this.path;
    }
    }
    public class Directory implements Node {
    private String path;
    private List children = new ArrayList<>();
    public Directory(String path) {
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = path;
    }
    public Directory(Directory parent, String path) {
    if (parent == null)
    throw new RuntimeException("输入的parent不正确!");
    if (path == null || path == "")
    throw new RuntimeException("输入的path不正确!");
    this.path = parent.getPath() + path;
    parent.add(this);
    }
    public boolean add(Node target) {
    for (Node node : children)
    // 不能创建同名文件
    if (target.getPath().equals(node.getPath())) return false;
    children.add(target);
    return true;
    }
    public boolean remove(Node target) {
    for (Node node : children)
    if (target.getPath().equals(node.getPath())) {
    children.remove(node);
    return true;
    }
    return false;
    }
    public String getPath() {
    return this.path;
    }
    public List getChildren() {
    return this.children;
    }
    }

    修改Node接口的抽象方法后代码清爽了很多。客户端调用需要稍微修改下。

    public class Client {
    public static void main(String[] args) {
    // 创建各级目录
    Directory root = new Directory("/root");
    Directory home = new Directory(root, "/home");
    Directory user1 = new Directory(home, "/user1");
    Directory text = new Directory(user1, "/text");
    Directory image = new Directory(user1, "/image");
    Directory png = new Directory(image, "/png");
    Directory gif = new Directory(image, "/gif");
    // 创建文件
    File f1 = new File(text, "/f1.txt");
    File f2 = new File(text, "/f2.txt");
    File f3 = new File(png, "/f3.png");
    File f4 = new File(gif, "/f4.gif");
    File f5 = new File(png, "/f5.png");
    // 输出root下的文件或者目录路径
    print(root);
    }
    public static void print(Directory root) {
    System.out.println(root.getPath());
    List nodes = root.getChildren();
    for (int i = 0; i < nodes.size(); i ++) {
    Node node = nodes.get(i);
    if (nodes.get(i) instanceof File) {
    System.out.println(node.getPath());
    continue;
    }
    print((Directory) node); // 增加强转
    }
    }
    }

    其实透明组合模式和安全组合模式看着用就好了,其实问题不大的。

    总结

    优点

    1. 让客户端可以一致地处理单一对象和组合对象。

    缺点

    1. 局限性太强,只有可以构成树形结构的对象集合才可以使用。

    适用场景

    1. 只有在对象集合可以组合成树形结构时才可以使用。
  • 相关阅读:
    python main 函数-启动-传递参数 python 打包 exe C# 进程传参
    数据结构与算法-----顺序表(链表篇)
    Hadoop-Yarn-ResourceManagerHA
    C++&QT 作业8
    Docker的常用命令||Docker是个流行的容器化平台,它允许你打包、分发和运行应用程序。
    你绝对不知道的接口加密解决方案:Python的各种加密实现
    ST7789-TFT屏幕驱动(整理有stm32/51单片机/arduino等驱动代码)
    基于Vue的预约停车位APP设计与实现
    View#post(Runnable)的执行流程
    一起Talk Android吧(第三百八十三回:视图绑定-ViewBinding)
  • 原文地址:https://www.cnblogs.com/buzuweiqi/p/16729556.html