简述
将对象集合组合成树形结构,使客户端可以以一致的方式处理单个对象(叶子节点)和组合对象(根节点)
话不多说,上优化案例。
优化案例
最初版v0
不使用组合模式。
现有一个文件和目录的管理模块。如样例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| 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; } }
|
不使用组合模式,我们来看看客户端的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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"); 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
方法的实现比较复杂,因为File
和Directory
是完全不同类型,所以只能对其分别处理。
如何让客户端对于File
和Directory
采用一致的处理方式?用组合模式啊!!!
修改版v1(透明组合模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| public interface 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; } }
|
通过在File
和Directory
的高层新增Node
接口,面向接口编程加上File
和Directory
形成的树形结构使得客户端可以很自然地一致处理File
和Directory
。来看看客户端代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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"); 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(安全组合模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public interface 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
接口的抽象方法后代码清爽了很多。客户端调用需要稍微修改下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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"); 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); } } }
|
其实透明组合模式和安全组合模式看着用就好了,其实问题不大的。
总结
优点
- 让客户端可以一致地处理单一对象和组合对象。
缺点
- 局限性太强,只有可以构成树形结构的对象集合才可以使用。
适用场景
- 只有在对象集合可以组合成树形结构时才可以使用。