個(gè)人學(xué)習(xí)筆記分享,當(dāng)前能力有限朽基,請(qǐng)勿貶低布隔,菜鳥互學(xué),大佬繞道
如有勘誤稼虎,歡迎指出和討論衅檀,本文后期也會(huì)進(jìn)行修正和補(bǔ)充
前言
這個(gè)是我隨手截的文件目錄,這樣的結(jié)構(gòu)都很眼熟吧霎俩?
一個(gè)個(gè)文件哀军,組成一個(gè)文件夾沉眶,文件和文件夾又可以組成更大的文件夾,進(jìn)而形成一個(gè)樹形結(jié)構(gòu)
那么排苍,我們?cè)邳c(diǎn)開的時(shí)候沦寂,需要先確認(rèn)目標(biāo)是文件夾或者文件,文件就打開淘衙,文件夾則是展開下一級(jí)
也就是說我們對(duì)于"部分"與“整體”采取了不同的方案传藏,但是這樣帶來了一些不必要的麻煩,我們只想打開這個(gè)目標(biāo)彤守,具體怎么打開那是你們自己的事情
進(jìn)而言之毯侦,客戶只關(guān)心對(duì)目標(biāo)進(jìn)行操作,并不關(guān)心因?yàn)槟繕?biāo)不同而導(dǎo)致的差異具垫。用戶對(duì)于目標(biāo)的“部分”與“整體”是一致對(duì)待的侈离。
比如,刪除目標(biāo)筝蚕,客戶只需要點(diǎn)擊刪除即可卦碾,并不關(guān)心具體邏輯,實(shí)際上如果是文件就直接刪除起宽,如果是文件夾需要先刪除下一層級(jí)的所有目標(biāo)洲胖。
復(fù)制、粘貼坯沪、移動(dòng)這類操作同理
換個(gè)例子绿映,我們告訴一個(gè)部門明天放假,只需要告知負(fù)責(zé)人即可
至于這個(gè)部門腐晾,包括一整個(gè)公司分部叉弦,還是研發(fā)部,還是研發(fā)小組藻糖,還是說就負(fù)責(zé)人一個(gè)人淹冰,我么并不需要關(guān)心,這是負(fù)責(zé)人該關(guān)心的事情
事實(shí)上如果這個(gè)部門不止負(fù)責(zé)人一個(gè)人的話颖御,他大概率也是轉(zhuǎn)告下一層部門的負(fù)責(zé)人而已(套娃榄棵??)
因此潘拱,你看,我們告知一個(gè)人拧略,或者告知任何一個(gè)部門芦岂,都是一樣的,并不需要先確定是哪層的負(fù)責(zé)人
這就是組合模式垫蛆,又稱部分整體模式禽最,用于將一組相似的對(duì)象作為單一的對(duì)象整體腺怯,進(jìn)而將部分與整體構(gòu)造成樹形結(jié)構(gòu)。
這種模式創(chuàng)建了一個(gè)包含自己對(duì)象組的類川无,并提供操作相同對(duì)象組的方式呛占。
組合模式定義了如何將容器對(duì)象和葉子對(duì)象進(jìn)行==遞歸==組合,使得客戶在使用的過程中無須進(jìn)行區(qū)分懦趋,可以對(duì)他們進(jìn)行一致的處理
1.介紹
使用目的:將對(duì)象組合成樹形結(jié)構(gòu)以表示"部分-整體"的層次結(jié)構(gòu)晾虑,進(jìn)而使客戶可能夠?qū)误w對(duì)象或者組合對(duì)象的使用具有一致性
使用時(shí)機(jī):希望用戶能夠忽略組合對(duì)象與單個(gè)對(duì)象的區(qū)別,進(jìn)行統(tǒng)一的處理
解決問題:將“部分”與“整體”區(qū)別對(duì)待會(huì)帶來不必要的麻煩
實(shí)現(xiàn)方法:將容器對(duì)象和葉子對(duì)象進(jìn)行==遞歸==組合仅叫,使得客戶在使用的過程中無須進(jìn)行區(qū)分帜篇,可以對(duì)他們進(jìn)行一致的處理
應(yīng)用實(shí)例:
- 對(duì)于文件/文件夾的刪除、復(fù)制诫咱、剪切笙隙、粘貼、移動(dòng)等操作
- 向一個(gè)個(gè)人/部門傳遞消息或者指令坎缭,只需要告知負(fù)責(zé)人即可
- 需求展示一個(gè)無限層級(jí)的目錄竟痰,如圖書管理系統(tǒng)(曾經(jīng)遇到的需求,層級(jí)未知掏呼,最后干脆做成了無限層級(jí))
優(yōu)點(diǎn):
- 客戶對(duì)于“部分”和“整體”的操作具有一致性坏快,無疑提高了用戶體驗(yàn)
- 高層代碼調(diào)用簡單方便,也簡化了客戶端代碼
- 節(jié)點(diǎn)自由度增加哄尔,可以選擇僅變更自己假消,或變更所有子節(jié)點(diǎn)
- 組合內(nèi)部增加新的節(jié)點(diǎn)很方便,不需要修改結(jié)構(gòu)的源代碼岭接,滿足“開閉原則”
缺點(diǎn):
- 所有節(jié)點(diǎn)都是實(shí)現(xiàn)類富拗,而不是接口,違背了依賴倒置原則
- 設(shè)計(jì)較為復(fù)雜鸣戴,需要理清不同層級(jí)之間的關(guān)系
- 難以使用集成的方法進(jìn)行擴(kuò)展
2.結(jié)構(gòu)
主要包含3個(gè)角色
-
抽象構(gòu)件(Component)角色:聲明樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)的公共接口啃沪,并實(shí)現(xiàn)默認(rèn)行為。
根據(jù)是否聲明訪問和管理子類的接口窄锅,分為透明模式和安全模式
樹葉構(gòu)件(Leaf)角色:組合中的葉節(jié)點(diǎn)對(duì)象创千,它沒有子節(jié)點(diǎn),用于實(shí)現(xiàn)抽象構(gòu)件角色中聲明的公共接口入偷。
樹枝構(gòu)件(Composite)角色:組合中的分支節(jié)點(diǎn)對(duì)象追驴,它有子節(jié)點(diǎn)。它實(shí)現(xiàn)了抽象構(gòu)件角色中聲明的接口疏之,同時(shí)還需要存儲(chǔ)和管理子節(jié)點(diǎn)殿雪。
其實(shí)也可以將三者融為一體,一個(gè)類就搞定了锋爪,但是不利于擴(kuò)展丙曙,功能較少的時(shí)候可以這樣做
圖就不花了爸业,這套娃的結(jié)構(gòu)根本無從下手
3.實(shí)現(xiàn)
這里給出三種示例,分別是簡單組合模式亏镰、透明模式和安全模式
3.1.簡單組合模式
不利于擴(kuò)展扯旷,但代碼簡單,適用于功能較少的機(jī)構(gòu)
之所以列出來說索抓,是因?yàn)檫@種其實(shí)才是最常見的钧忽,尤其是算法中經(jīng)常用到,包括鏈表也是使用的這種結(jié)構(gòu)
3.1.1.示例1
模擬業(yè)務(wù)如下:
- 鏈表由節(jié)點(diǎn)連接構(gòu)成
- 每個(gè)節(jié)點(diǎn)存儲(chǔ)一個(gè)值和下一個(gè)節(jié)點(diǎn)纸兔,下一個(gè)節(jié)點(diǎn)可能為空
代碼很簡單直接貼了惰瓜,經(jīng)常刷算法題的都能默寫下來了
package com.company.test.composite;
import lombok.Data;
@Data
class Node {
public int val;
public Node next;
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
public void show() {
if (next == null) {
//為最后一個(gè)節(jié)點(diǎn),打印本身的值并轉(zhuǎn)行
System.out.println(val);
} else {
//不為最后一個(gè)節(jié)點(diǎn)汉矿,打印本身的值崎坊,并打印下一個(gè)節(jié)點(diǎn)
System.out.print(val + " -> ");
next.show();
}
}
}
public class SimpleCompositeTest {
public static void main(String[] args) {
Node node1 = new Node(1, null);
Node node2 = new Node(2, node1);
Node node3 = new Node(3, node2);
Node node4 = new Node(4, node3);
node1.show();
node2.show();
node3.show();
node4.show();
}
}
運(yùn)行結(jié)果
我們?cè)谶@里舍棄了抽象構(gòu)建,而且樹葉構(gòu)件和樹枝構(gòu)件使用同一個(gè)類實(shí)現(xiàn)即可洲拇,通過
next
是否為空判斷是否是葉子節(jié)點(diǎn)
3.1.2.示例2
模擬業(yè)務(wù)如下
- 職員信息包括4個(gè)數(shù)據(jù):姓名奈揍、職位、薪水赋续、下級(jí)人員
- 職員信息提供接口進(jìn)行打印男翰,可以打印當(dāng)前職員信息,也可以同時(shí)打印所有下級(jí)人員信息
結(jié)構(gòu)與示例1類似纽乱,故不多做解釋
package com.company.test.composite;
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Data
class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinate;
public Employee(String name, String dept, int salary, List<Employee> subordinate) {
this.name = name;
this.dept = dept;
this.salary = salary;
this.subordinate = subordinate;
}
public String toString() {
String subordinateNames = subordinate.stream().map(Employee::getName).collect(Collectors.joining(", "));
return ("Employee :[ "
+ "Name : " + name
+ ", dept : " + dept
+ ", salary : " + salary
+ ", subordinate : " + subordinateNames
+ " ]");
}
public void showCurrent() {
System.out.println(this.toString());
}
public void showAll() {
showCurrent();
subordinate.forEach((m) -> {
m.showAll();
});
}
}
public class SimpleCompositeTest1 {
public static void main(String[] args) {
Employee clerk1 = new Employee("clerk1", "clerk", 10000, new ArrayList<>());
Employee clerk2 = new Employee("clerk2", "clerk", 10000, new ArrayList<>());
Employee manager1 = new Employee("manager1", "manager", 50000, Arrays.asList(new Employee[]{clerk1, clerk2}));
Employee manager2 = new Employee("manager2", "manager", 50000, new ArrayList<>());
Employee ceo = new Employee("Jobs", "ceo", 150000, Arrays.asList(new Employee[]{manager1, manager2}));
ceo.showAll();
}
}
運(yùn)行結(jié)果
3.2.透明組合模式
透明模式是把組合使用的方法放到抽象類中蛾绎,不管葉子對(duì)象還是樹枝對(duì)象都有相同的結(jié)構(gòu)
這樣做的好處就是葉子節(jié)點(diǎn)和樹枝節(jié)點(diǎn)對(duì)于外界沒有區(qū)別,它們具備完全一致的行為接口鸦列。
但因?yàn)長eaf類本身不具備add()租冠、remove()方法的功能,所以實(shí)現(xiàn)它是沒有意義的
-
定義抽象構(gòu)件角色
Component
abstract class Component { protected String name; public Component(String name) { this.name = name; } public abstract void add(Component component); public abstract void remove(Component component); public abstract List<Component> getChildren(); public abstract void show(int depth); }
-
定義樹葉構(gòu)件
Leaf
class Leaf extends Component { public Leaf(String name) { super(name); } @Override public void add(Component component) { //空實(shí)現(xiàn)薯嗤,拋出“不支持請(qǐng)求”異常 throw new UnsupportedOperationException(); } @Override public void remove(Component component) { //空實(shí)現(xiàn)顽爹,拋出“不支持請(qǐng)求”異常 throw new UnsupportedOperationException(); } @Override public List<Component> getChildren() { return null; } @Override public void show(int depth) { while (depth-- > 0) { System.out.print("-"); } System.out.println(name); } }
-
定義樹枝構(gòu)件
Composite
class Composite extends Component { List<Component> children = new ArrayList<>(); public Composite(String name) { super(name); } @Override public void add(Component component) { children.add(component); } @Override public void remove(Component component) { children.remove(component); } @Override public List<Component> getChildren() { return children; } @Override public void show(int depth) { int nowDepth = depth; while (depth-- > 0) { System.out.print("-"); } System.out.println(name); children.forEach(m -> { m.show(nowDepth + 1); }); } }
完整代碼
package com.company.test.composite;
import java.util.ArrayList;
import java.util.List;
abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract List<Component> getChildren();
public abstract void show(int depth);
}
class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void add(Component component) {
//空實(shí)現(xiàn),拋出“不支持請(qǐng)求”異常
throw new UnsupportedOperationException();
}
@Override
public void remove(Component component) {
//空實(shí)現(xiàn)骆姐,拋出“不支持請(qǐng)求”異常
throw new UnsupportedOperationException();
}
@Override
public List<Component> getChildren() {
return null;
}
@Override
public void show(int depth) {
while (depth-- > 0) {
System.out.print("-");
}
System.out.println(name);
}
}
class Composite extends Component {
List<Component> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public List<Component> getChildren() {
return children;
}
@Override
public void show(int depth) {
int nowDepth = depth;
while (depth-- > 0) {
System.out.print("-");
}
System.out.println(name);
children.forEach(m -> {
m.show(nowDepth + 1);
});
}
}
public class ClearCompositeTest {
public static void main(String[] args) {
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
Component composite1=new Composite("composite1");
composite1.add(leaf1);
composite1.add(leaf2);
Component leaf3 = new Leaf("leaf3");
Component composite3=new Composite("composite3");
composite3.add(composite1);
composite3.add(leaf3);
composite3.show(1);
}
}
運(yùn)行結(jié)果
如圖镜粤,組裝了一個(gè)目錄,并將其打印出來
可以看到玻褪,樹葉和樹枝擁有同樣的功能肉渴,但樹葉的部分功能并沒有正常執(zhí)行(拋出異常或空實(shí)現(xiàn))带射,這樣會(huì)帶來安全性問題
安全組合模式就是為了解決這種情況
3.3.安全組合模式
在該方式中黄虱,將管理子構(gòu)件的方法移到樹枝構(gòu)件中,抽象構(gòu)件和樹葉構(gòu)件沒有對(duì)子對(duì)象的管理方法
這樣就避免了上一種方式的安全性問題庸诱,但由于葉子和分支有不同的接口捻浦,客戶端在調(diào)用時(shí)要知道樹葉對(duì)象和樹枝對(duì)象的存在,所以失去了透明性
結(jié)構(gòu)一樣的桥爽,就直接貼代碼了朱灿,自己對(duì)比一下
package com.company.test.composite;
import java.util.ArrayList;
import java.util.List;
abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract List<Component> getChildren();
public abstract void show(int depth);
}
class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void add(Component component) {
//空實(shí)現(xiàn),拋出“不支持請(qǐng)求”異常
throw new UnsupportedOperationException();
}
@Override
public void remove(Component component) {
//空實(shí)現(xiàn)钠四,拋出“不支持請(qǐng)求”異常
throw new UnsupportedOperationException();
}
@Override
public List<Component> getChildren() {
return null;
}
@Override
public void show(int depth) {
while (depth-- > 0) {
System.out.print("-");
}
System.out.println(name);
}
}
class Composite extends Component {
List<Component> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public List<Component> getChildren() {
return children;
}
@Override
public void show(int depth) {
int nowDepth = depth;
while (depth-- > 0) {
System.out.print("-");
}
System.out.println(name);
children.forEach(m -> {
m.show(nowDepth + 1);
});
}
}
public class ClearCompositeTest {
public static void main(String[] args) {
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
Component composite1=new Composite("composite1");
composite1.add(leaf1);
composite1.add(leaf2);
Component leaf3 = new Leaf("leaf3");
Component composite3=new Composite("composite3");
composite3.add(composite1);
composite3.add(leaf3);
composite3.show(1);
}
}
運(yùn)行結(jié)果
4.透明組合模式與安全組合模式的區(qū)別
透明模式:
- 只需要在定義的時(shí)候確定是樹葉或者樹枝盗扒,使用的時(shí)候樹葉和樹枝可以當(dāng)做同一個(gè)對(duì)象使用
- 樹葉實(shí)現(xiàn)了所有功能,但部分功能實(shí)際上并不擁有缀去,需要拋出異陈略睿或者空實(shí)現(xiàn),會(huì)帶來安全性問題
安全模式:
- 使用時(shí)需要知道是樹葉或者樹枝缕碎,部分功能可能存在差異
- 所有功能都正常實(shí)現(xiàn)了褥影,所以不會(huì)帶來透明模式的安全性問題
- 因?yàn)樾枰朗枪?jié)點(diǎn)類型,使用不便咏雌,一定程度上違背了初衷
簡單點(diǎn)說凡怎,一種是葉節(jié)點(diǎn)與樹枝節(jié)點(diǎn)具備一致的行為接口但有空實(shí)現(xiàn)的透明模式,另一種是樹枝節(jié)點(diǎn)單獨(dú)擁有用來組合的方法但調(diào)用不便的安全模式
使用哪種赊抖,自行取舍咯统倒,如果是圖方便,簡單組合模式就可以滿足很多需求氛雪,如果需要保證安全房匆,就需要使用安全組合模式,但是最符合初衷的應(yīng)該是透明組合模式
5.擴(kuò)展使用
5.1.將節(jié)點(diǎn)進(jìn)一步抽象化
模擬文件夾目錄报亩,包括文件夾和文件
package com.company.test.composite;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Data
abstract class Files {
protected String name;
public Files(String name) {
this.name = name;
}
public abstract void check();
public abstract void copyFiles();
}
abstract class File extends Files {
public File(String name) {
super(name);
}
@Override
public void copyFiles() {
System.out.println("copy file: " + name);
}
}
class Text extends File {
public Text(String text) {
super(text);
}
@Override
public void check() {
System.out.println("show text: " + name);
}
}
class Mp3 extends File {
public Mp3(String name) {
super(name);
}
@Override
public void check() {
System.out.println("play mp3: " + name);
}
}
class Folder extends Files {
public Folder(String name) {
super(name);
}
List<Files> subordinateFiles = new ArrayList<>();
public void addFiles(Files files) {
subordinateFiles.add(files);
}
public void removeFiles(Files files) {
subordinateFiles.remove(files);
}
@Override
public void check() {
String subordinateFileNames = subordinateFiles.stream().map(m -> m.getName()).collect(Collectors.joining(", "));
System.out.println("open folder: " + name + ", subordinateFiles: " + subordinateFileNames);
}
@Override
public void copyFiles() {
subordinateFiles.forEach(m -> {
m.copyFiles();
});
System.out.println("copy folder: " + name);
}
}
public class FileCompositeTest {
public static void main(String[] args) {
Text text = new Text("HelloWorld.text");
Mp3 mp3 = new Mp3("我在昨天的夢(mèng)里又看見了你.mp3");
Text lyric = new Text("我在昨天的夢(mèng)里又看見了你.text");
Folder folder = new Folder("我在昨天的夢(mèng)里又看見了你");
folder.addFiles(mp3);
folder.addFiles(lyric);
Folder folder1 = new Folder("empty");
Folder root = new Folder("root");
root.addFiles(folder);
root.addFiles(folder1);
root.addFiles(text);
System.out.println("<---------------------------操作文件夾:root------------------------------->");
root.check();
root.copyFiles();
System.out.println("<---------------------------操作文件夾:我在昨天的夢(mèng)里又看見了你------------------------------->");
folder.check();
folder.copyFiles();
System.out.println("<---------------------------操作文件:我在昨天的夢(mèng)里又看見了你.mp3------------------------------->");
mp3.check();
mp3.copyFiles();
}
}
運(yùn)行結(jié)果
文件目錄(模擬)
看浴鸿,無論是mp3
文件,或者text
文件捆昏,或者folder
文件夾赚楚,我們都可以執(zhí)行同樣的check()
和copyFiles()
操作
其余擴(kuò)展使用后續(xù)再追加,暫時(shí)只想到這里就寫到這里
后記
將相似的目標(biāo)提取其共同點(diǎn)骗卜,從而可以進(jìn)行部分一致性操作宠页,而目標(biāo)本身只需要關(guān)注自己的特點(diǎn),將共同點(diǎn)交由接口或者父類處理
其實(shí)這也是多態(tài)和繼承的目的寇仓,所以從學(xué)習(xí)Java開始举户,我們其實(shí)就在按照這種思想設(shè)計(jì)程序,組合模式不過是其中一種方案而已
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
個(gè)人站點(diǎn):在搭了在搭了遍烦。俭嘁。。(右鍵 - 新建文件夾)