簡述 類型:結構型 目的:將對象集合組合成樹形結構,使客戶端可以以一致的方式處理單個對象(葉子節點)和組合對象(根節點) 話不多說,上優化案例。 優化案例 最初版v0 不使用組合模式。 現有一個文件和目錄的管理模塊。如樣例。 public class File { // 文件類 private St ...
簡述
- 類型:結構型
- 目的:將對象集合組合成樹形結構,使客戶端可以以一致的方式處理單個對象(葉子節點)和組合對象(根節點)
話不多說,上優化案例。
優化案例
最初版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); // 增加強轉
}
}
}
其實透明組合模式和安全組合模式看著用就好了,其實問題不大的。
總結
優點
- 讓客戶端可以一致地處理單一對象和組合對象。
缺點
- 局限性太強,只有可以構成樹形結構的對象集合才可以使用。
適用場景
- 只有在對象集合可以組合成樹形結構時才可以使用。
本文來自博客園,作者:buzuweiqi,轉載請註明原文鏈接:https://www.cnblogs.com/buzuweiqi/p/16729556.html