简述
- 类型:结构型
- 目的:将对象集合组合成树形结构,使客户端可以以一致的方式处理单个对象(叶子节点)和组合对象(根节点)
话不多说,上优化案例。
优化案例
最初版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方法的实现比较复杂,因为File和Directory是完全不同类型,所以只能对其分别处理。
如何让客户端对于File和Directory采用一致的处理方式?用组合模式啊!!!
修改版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;
- }
- }
复制代码 通过在File和Directory的高层新增Node接口,面向接口编程加上File和Directory形成的树形结构使得客户端可以很自然地一致处理File和Directory。来看看客户端代码。- 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); // 增加强转
- }
- }
- }
复制代码 其实透明组合模式和安全组合模式看着用就好了,其实问题不大的。
总结
优点
缺点
- 局限性太强,只有可以构成树形结构的对象集合才可以使用。
适用场景
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |