设计模式---组合模式

打印 上一主题 下一主题

主题 725|帖子 725|积分 2175

简述


  • 类型:结构型
  • 目的:将对象集合组合成树形结构,使客户端可以以一致的方式处理单个对象(叶子节点)组合对象(根节点)
话不多说,上优化案例。
优化案例

最初版v0

不使用组合模式。
现有一个文件和目录的管理模块。如样例。
  1. public class File { // 文件类
  2.     private String path;
  3.     private Directory parent;
  4.     public File(Directory dir, String path) {
  5.         if (dir == null)
  6.             throw new RuntimeException("输入的dir不正确!");
  7.         if (path == null || path == "")
  8.             throw new RuntimeException("输入的path不正确!");
  9.         this.parent = dir;
  10.         this.path = dir.getPath() + path;
  11.         dir.add(this);
  12.     }
  13.     public String getPath() {
  14.         return this.path;
  15.     }
  16. }
  17. public class Directory { // 目录类
  18.     private String path;
  19.     private List<Directory> dirs = new ArrayList<>();
  20.     private List<File> files = new ArrayList<>();
  21.     public Directory(String path) {
  22.         if (path == null || path == "")
  23.             throw new RuntimeException("输入的path不正确!");
  24.         this.path = path;
  25.     }
  26.     public Directory(Directory parent, String path) {
  27.         if (parent == null)
  28.             throw new RuntimeException("输入的parent不正确!");
  29.         if (path == null || path == "")
  30.             throw new RuntimeException("输入的path不正确!");
  31.         this.path = parent.getPath() + path;
  32.         parent.add(this);
  33.     }
  34.     public boolean add(File target) {
  35.         for (File file : files)
  36.             // 不能创建同名文件
  37.             if (target.getPath().equals(file.getPath())) return false;
  38.         files.add(target);
  39.         return true;
  40.     }
  41.     public boolean add(Directory target) {
  42.         for (Directory dir : dirs)
  43.             // 不能创建同名目录
  44.             if (target.getPath().equals(dir.getPath())) return false;
  45.         dirs.add(target);
  46.         return true;
  47.     }
  48.     public boolean remove(Directory target) {
  49.         for (Directory dir : dirs)
  50.             if (target.getPath().equals(dir.getPath())) {
  51.                 dirs.remove(dir);
  52.                 return true;
  53.             }
  54.         return false;
  55.     }
  56.     public boolean remove(File target) {
  57.         for (File file : files)
  58.             if (target.getPath().equals(file.getPath())) {
  59.                 files.remove(file);
  60.                 return true;
  61.             }
  62.         return false;
  63.     }
  64.     public String getPath() {
  65.         return this.path;
  66.     }
  67.     public List<Directory> getDirs() {
  68.         return this.dirs;
  69.     }
  70.     public List<File> getFiles() {
  71.         return this.files;
  72.     }
  73. }
复制代码
不使用组合模式,我们来看看客户端的使用。
  1. public class Client { // 客户端
  2.     public static void main(String[] args) {
  3.         // 创建各级目录
  4.         Directory root = new Directory("/root");
  5.         Directory home = new Directory(root, "/home");
  6.         Directory user1 = new Directory(home, "/user1");
  7.         Directory text = new Directory(user1, "/text");
  8.         Directory image = new Directory(user1, "/image");
  9.         Directory png = new Directory(image, "/png");
  10.         Directory gif = new Directory(image, "/gif");
  11.         // 创建文件
  12.         File f1 = new File(text, "/f1.txt");
  13.         File f2 = new File(text, "/f2.txt");
  14.         File f3 = new File(png, "/f3.png");
  15.         File f4 = new File(gif, "/f4.gif");
  16.         File f5 = new File(png, "/f5.png");
  17.         // 输出root下的文件或者目录路径
  18.         print(root);
  19.     }
  20.     // 前序遍历目录下路径
  21.     public static void print(Directory root) {
  22.         System.out.println(root.getPath());
  23.         List<Directory> dirs = root.getDirs();
  24.         List<File> files = root.getFiles();
  25.         for (int i = 0; i < dirs.size(); i ++) {
  26.             print(dirs.get(i));
  27.         }
  28.         for (int i = 0; i < files.size(); i ++) {
  29.             System.out.println(files.get(i).getPath());
  30.         }
  31.     }
  32. }
复制代码
可以看到print方法的实现比较复杂,因为File和Directory是完全不同类型,所以只能对其分别处理。
如何让客户端对于File和Directory采用一致的处理方式?用组合模式啊!!!
修改版v1(透明组合模式)
  1. public interface Node { // 从File和Directory中抽象出Node类
  2.     boolean add(Node node);
  3.     boolean remove(Node node);
  4.     List<Node> getChildren();
  5.     String getPath();
  6. }
  7. public class File implements Node {
  8.     private String path;
  9.     private Node parent;
  10.     public File(Node parent, String path) {
  11.         if (parent == null)
  12.             throw new RuntimeException("输入的dir不正确!");
  13.         if (path == null || path == "")
  14.             throw new RuntimeException("输入的path不正确!");
  15.         this.parent = parent;
  16.         this.path = parent.getPath() + path;
  17.         parent.add(this);
  18.     }
  19.     public boolean add(Node node) { // 因为不是容器,所以重写这个方法无意义
  20.         throw new RuntimeException("不支持此方法!");
  21.     }
  22.     public boolean remove(Node node) { // 同上
  23.         throw new RuntimeException("不支持此方法!");
  24.     }
  25.     public List<Node> getChildren() { // 同上
  26.         throw new RuntimeException("不支持此方法!");
  27.     }
  28.     public String getPath() {
  29.         return this.path;
  30.     }
  31. }
  32. public class Directory implements Node {
  33.     private String path;
  34.     private List<Node> children = new ArrayList<>();
  35.     public Directory(String path) {
  36.         if (path == null || path == "")
  37.             throw new RuntimeException("输入的path不正确!");
  38.         this.path = path;
  39.     }
  40.     public Directory(Node parent, String path) {
  41.         if (parent == null)
  42.             throw new RuntimeException("输入的parent不正确!");
  43.         if (path == null || path == "")
  44.             throw new RuntimeException("输入的path不正确!");
  45.         this.path = parent.getPath() + path;
  46.         parent.add(this);
  47.     }
  48.     public boolean add(Node target) {
  49.         for (Node node : children)
  50.             // 不能创建同名文件
  51.             if (target.getPath().equals(node.getPath())) return false;
  52.         children.add(target);
  53.         return true;
  54.     }
  55.     public boolean remove(Node target) {
  56.         for (Node node : children)
  57.             if (target.getPath().equals(node.getPath())) {
  58.                 children.remove(node);
  59.                 return true;
  60.             }
  61.         return false;
  62.     }
  63.     public String getPath() {
  64.         return this.path;
  65.     }
  66.     public List<Node> getChildren() {
  67.         return this.children;
  68.     }
  69. }
复制代码
通过在File和Directory的高层新增Node接口,面向接口编程加上File和Directory形成的树形结构使得客户端可以很自然地一致处理File和Directory。来看看客户端代码。
  1. public class Client {
  2.     public static void main(String[] args) {
  3.         // 创建各级目录
  4.         Node root = new Directory("/root");
  5.         Node home = new Directory(root, "/home");
  6.         Node user1 = new Directory(home, "/user1");
  7.         Node text = new Directory(user1, "/text");
  8.         Node image = new Directory(user1, "/image");
  9.         Node png = new Directory(image, "/png");
  10.         Node gif = new Directory(image, "/gif");
  11.         // 创建文件
  12.         Node f1 = new File(text, "/f1.txt");
  13.         Node f2 = new File(text, "/f2.txt");
  14.         Node f3 = new File(png, "/f3.png");
  15.         Node f4 = new File(gif, "/f4.gif");
  16.         Node f5 = new File(png, "/f5.png");
  17.         // 输出root下的文件或者目录路径
  18.         print(root);
  19.     }
  20.     public static void print(Node root) {
  21.         System.out.println(root.getPath());
  22.         List<Node> nodes = root.getChildren();
  23.         for (int i = 0; i < nodes.size(); i ++) {
  24.             Node node = nodes.get(i);
  25.             if (node instanceof File) {
  26.                 System.out.println(node.getPath());
  27.                 continue;
  28.             }
  29.             print(node);
  30.         }
  31.     }
  32. }
复制代码
别高兴的太早了,虽然我们实现了最初的需求,但是有一处的代码不是很健康。在File中有三个方法实际上并没有被实现,有些臃肿。
修改版v2(安全组合模式)
  1. public interface Node { // 从File和Directory中抽象出Node类
  2.     String getPath(); // 删除累赘的方法
  3. }
  4. public class File implements Node {
  5.     private String path;
  6.     private Node parent;
  7.     public File(Directory parent, String path) {
  8.         if (parent == null)
  9.             throw new RuntimeException("输入的dir不正确!");
  10.         if (path == null || path == "")
  11.             throw new RuntimeException("输入的path不正确!");
  12.         this.parent = parent;
  13.         this.path = parent.getPath() + path;
  14.         parent.add(this);
  15.     }
  16.     public String getPath() {
  17.         return this.path;
  18.     }
  19. }
  20. public class Directory implements Node {
  21.     private String path;
  22.     private List<Node> children = new ArrayList<>();
  23.     public Directory(String path) {
  24.         if (path == null || path == "")
  25.             throw new RuntimeException("输入的path不正确!");
  26.         this.path = path;
  27.     }
  28.     public Directory(Directory parent, String path) {
  29.         if (parent == null)
  30.             throw new RuntimeException("输入的parent不正确!");
  31.         if (path == null || path == "")
  32.             throw new RuntimeException("输入的path不正确!");
  33.         this.path = parent.getPath() + path;
  34.         parent.add(this);
  35.     }
  36.     public boolean add(Node target) {
  37.         for (Node node : children)
  38.             // 不能创建同名文件
  39.             if (target.getPath().equals(node.getPath())) return false;
  40.         children.add(target);
  41.         return true;
  42.     }
  43.     public boolean remove(Node target) {
  44.         for (Node node : children)
  45.             if (target.getPath().equals(node.getPath())) {
  46.                 children.remove(node);
  47.                 return true;
  48.             }
  49.         return false;
  50.     }
  51.     public String getPath() {
  52.         return this.path;
  53.     }
  54.     public List<Node> getChildren() {
  55.         return this.children;
  56.     }
  57. }
复制代码
修改Node接口的抽象方法后代码清爽了很多。客户端调用需要稍微修改下。
  1. public class Client {
  2.     public static void main(String[] args) {
  3.         // 创建各级目录
  4.         Directory root = new Directory("/root");
  5.         Directory home = new Directory(root, "/home");
  6.         Directory user1 = new Directory(home, "/user1");
  7.         Directory text = new Directory(user1, "/text");
  8.         Directory image = new Directory(user1, "/image");
  9.         Directory png = new Directory(image, "/png");
  10.         Directory gif = new Directory(image, "/gif");
  11.         // 创建文件
  12.         File f1 = new File(text, "/f1.txt");
  13.         File f2 = new File(text, "/f2.txt");
  14.         File f3 = new File(png, "/f3.png");
  15.         File f4 = new File(gif, "/f4.gif");
  16.         File f5 = new File(png, "/f5.png");
  17.         // 输出root下的文件或者目录路径
  18.         print(root);
  19.     }
  20.     public static void print(Directory root) {
  21.         System.out.println(root.getPath());
  22.         List<Node> nodes = root.getChildren();
  23.         for (int i = 0; i < nodes.size(); i ++) {
  24.             Node node = nodes.get(i);
  25.             if (nodes.get(i) instanceof File) {
  26.                 System.out.println(node.getPath());
  27.                 continue;
  28.             }
  29.             print((Directory) node); // 增加强转
  30.         }
  31.     }
  32. }
复制代码
其实透明组合模式和安全组合模式看着用就好了,其实问题不大的。
总结

优点


  • 让客户端可以一致地处理单一对象和组合对象。
缺点


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


  • 只有在对象集合可以组合成树形结构时才可以使用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万万哇

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表