前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式---组合模式

设计模式---组合模式

作者头像
技术文章精选
发布2022-11-08 16:38:16
2400
发布2022-11-08 16:38:16
举报
文章被收录于专栏:优质文章优质文章

简述

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

话不多说,上优化案例。

优化案例

最初版v0

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

代码语言:javascript
复制
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<Directory> dirs = new ArrayList<>();    private List<File> 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<Directory> getDirs() {        return this.dirs;    }        public List<File> getFiles() {        return this.files;    }}

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

代码语言:javascript
复制
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<Directory> dirs = root.getDirs();        List<File> 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(透明组合模式)

代码语言:javascript
复制
public interface Node { // 从File和Directory中抽象出Node类    boolean add(Node node);    boolean remove(Node node);    List<Node> 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<Node> getChildren() { // 同上        throw new RuntimeException("不支持此方法!");    }        public String getPath() {        return this.path;    }} public class Directory implements Node {    private String path;    private List<Node> 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<Node> getChildren() {        return this.children;    }}

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

代码语言:javascript
复制
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<Node> 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(安全组合模式)

代码语言:javascript
复制
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<Node> 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<Node> getChildren() {        return this.children;    }}

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

代码语言:javascript
复制
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<Node> 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. 只有在对象集合可以组合成树形结构时才可以使用。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 简述
  • 优化案例
    • 最初版v0
      • 修改版v1(透明组合模式)
        • 修改版v2(安全组合模式)
        • 总结
          • 优点
            • 缺点
              • 适用场景
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档