设计模式—组合模式

简述

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

话不多说,上优化案例。

优化案例

最初版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<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;     } } 

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

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(透明组合模式)

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。来看看客户端代码。

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(安全组合模式)

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接口的抽象方法后代码清爽了很多。客户端调用需要稍微修改下。

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. 只有在对象集合可以组合成树形结构时才可以使用。
发表评论

相关文章