個(gè)人主頁(yè):金鱗踏雨
個(gè)人簡(jiǎn)介:大家好,我是金鱗,一個(gè)初出茅廬的Java小白
目前狀況:22屆普通本科畢業(yè)生,幾經(jīng)波折了,現(xiàn)在任職于一家國(guó)內(nèi)大型知名日化公司,從事Java開發(fā)工作
我的博客:這里是CSDN,是我學(xué)習(xí)技術(shù),總結(jié)知識(shí)的地方。希望和各位大佬交流,共同進(jìn)步 ~
本篇博客內(nèi)容來(lái)自"IT楠老師的設(shè)計(jì)模式~",出品時(shí)結(jié)合了個(gè)人理解~
比較特殊,所適用的場(chǎng)景比較狹窄!只有在構(gòu)建樹形結(jié)構(gòu)的時(shí)候才可能用到。
一、組合模式的原理與實(shí)現(xiàn)
在 GoF 的《設(shè)計(jì)模式》一書中,組合模式是這樣定義的:
Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.
翻譯過來(lái)就是:將一組對(duì)象組織(Compose)成樹形結(jié)構(gòu),以表示一種“部分 - 整體”的層次結(jié)構(gòu)。組合讓客戶端可以統(tǒng)一單個(gè)對(duì)象和組合對(duì)象的處理邏輯。
組合模式(Composite Pattern)是一種結(jié)構(gòu)型設(shè)計(jì)模式。在組合模式中,每個(gè)對(duì)象都有相同的接口,這使得客戶端不需要知道對(duì)象的具體類型,而只需要調(diào)用對(duì)象的通用接口即可。
組合模式涉及到的角色
- Component(抽象構(gòu)件):定義組合對(duì)象的通用接口,可以包含其他組合對(duì)象或葉子對(duì)象。
- Leaf(葉子節(jié)點(diǎn)):表示組合對(duì)象中的葉子節(jié)點(diǎn),它沒有子節(jié)點(diǎn)。
- Composite(組合節(jié)點(diǎn)):表示組合對(duì)象中的組合節(jié)點(diǎn),它可以包含其他組合對(duì)象或葉子對(duì)象。
案例一
下面是一個(gè)簡(jiǎn)單的組合模式示例代碼,用于表示文件系統(tǒng)中的文件和文件夾:
// Component(抽象構(gòu)件)
interface FileSystem {
void display();
}
// Leaf(葉子節(jié)點(diǎn))-- 文件
class File implements FileSystem {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("File: " + name);
}
}
// Composite(組合節(jié)點(diǎn)) -- 文件夾
class Folder implements FileSystem {
// 文件夾里面有 -- 文件、文件夾
private String name;
private List<FileSystem> children;
public Folder(String name) {
this.name = name;
children = new ArrayList<>();
}
public void add(FileSystem fileSystem) {
children.add(fileSystem);
}
public void remove(FileSystem fileSystem) {
children.remove(fileSystem);
}
@Override
public void display() {
System.out.println("Folder: " + name);
for (FileSystem fileSystem : children) {
fileSystem.display();
}
}
}
// 客戶端代碼
public class Client {
public static void main(String[] args) {
// 文件
FileSystem file1 = new File("file1.txt");
FileSystem file2 = new File("file2.txt");
// 文件夾
Folder folder1 = new Folder("folder1");
folder1.add(file1);
folder1.add(file2);
FileSystem file3 = new File("file3.txt");
FileSystem file4 = new File("file4.txt");
Folder folder2 = new Folder("folder2");
folder2.add(file3);
folder2.add(file4);
folder2.add(folder1);
folder2.display();
}
}
在這個(gè)示例中,F(xiàn)ileSystem 是抽象構(gòu)件,它定義了組合對(duì)象的通用接口 display。File 是葉子節(jié)點(diǎn),表示文件,它實(shí)現(xiàn)了 FileSystem 接口,并在 display 方法中輸出文件名。Folder 是組合節(jié)點(diǎn),表示文件夾,它實(shí)現(xiàn)了 FileSystem 接口,并維護(hù)了一個(gè)子節(jié)點(diǎn)列表 children,可以添加和刪除子節(jié)點(diǎn)。在 display 方法中,它首先輸出文件夾名,然后依次調(diào)用子節(jié)點(diǎn)的 display 方法輸出子節(jié)點(diǎn)信息。
在客戶端代碼中,我們創(chuàng)建了一些文件和文件夾,然后將它們組合成了一個(gè)樹形結(jié)構(gòu),最后調(diào)用根最后調(diào)用根節(jié)點(diǎn)(即 folder2)的 display 方法,輸出了整個(gè)文件系統(tǒng)的信息。這樣,我們就可以通過組合模式,使用相同的方式來(lái)處理單個(gè)文件和整個(gè)文件系統(tǒng)。
案例一(進(jìn)階)
那接下來(lái)我們將文件目錄的案例做一個(gè)升級(jí),如何設(shè)計(jì)實(shí)現(xiàn)支持遞歸遍歷的文件系統(tǒng)目錄樹結(jié)構(gòu)?
假設(shè)我們有這樣一個(gè)需求,設(shè)計(jì)一個(gè)類來(lái)表示文件系統(tǒng)中的目錄,能方便地實(shí)現(xiàn)下面這些功能:
- 動(dòng)態(tài)地添加、刪除某個(gè)目錄下的子目錄或文件
- 統(tǒng)計(jì)指定目錄下的文件個(gè)數(shù)
- 統(tǒng)計(jì)指定目錄下的文件總大小
我這里給出了這個(gè)類的骨架代碼,在下面的代碼實(shí)現(xiàn)中,我們把文件和目錄統(tǒng)一用 FileSystemNode 類來(lái)表示,并且通過 isFile 屬性來(lái)區(qū)分。
// 文件 與 目錄
public class FileSystemNode {
private String path;
// 標(biāo)識(shí)區(qū)分(文件 -- 目錄)
private boolean isFile;
private List<FileSystemNode> subNodes = new ArrayList<>();
public FileSystemNode(String path, boolean isFile) {
this.path = path;
this.isFile = isFile;
}
public int countNumOfFiles() {
if (isFile) {
return 1;
}
int numOfFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
numOfFiles += fileOrDir.countNumOfFiles();
}
return numOfFiles;
}
public long countSizeOfFiles() {
if (isFile) {
File file = new File(path);
if (!file.exists()) return 0;
return file.length();
}
long sizeofFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
sizeofFiles += fileOrDir.countSizeOfFiles();
}
return sizeofFiles;
}
public String getPath() {
return path;
}
public void addSubNode(FileSystemNode fileOrDir) {
subNodes.add(fileOrDir);
}
public void removeSubNode(FileSystemNode fileOrDir) {
int size = subNodes.size();
for (int i = 0; i < size; ++i) {
if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
subNodes.remove(i);
i--;
}
}
}
}
單純從功能實(shí)現(xiàn)角度來(lái)說,上面的代碼沒有問題,已經(jīng)實(shí)現(xiàn)了我們想要的功能。但是,如果我們開發(fā)的是一個(gè)大型系統(tǒng),從擴(kuò)展性(文件或目錄可能會(huì)對(duì)應(yīng)不同的操作)、業(yè)務(wù)建模(文件和目錄從業(yè)務(wù)上是兩個(gè)概念)、代碼的可讀性(文件和目錄區(qū)分對(duì)待更加符合人們對(duì)業(yè)務(wù)的認(rèn)知)的角度來(lái)說,我們最好對(duì)文件和目錄進(jìn)行區(qū)分設(shè)計(jì),定義為 File 和 Directory 兩個(gè)類。
按照這個(gè)設(shè)計(jì)思路,我們對(duì)代碼進(jìn)行重構(gòu)。
重構(gòu)之后的代碼如下所示:
public abstract class FileSystemNode {
protected String path;
public FileSystemNode(String path) {
this.path = path;
}
public abstract int countNumOfFiles();
public abstract long countSizeOfFiles();
public String getPath() {
return path;
}
}
public class File extends FileSystemNode {
public File(String path) {
super(path);
}
@Override
public int countNumOfFiles() {
return 1;
}
@Override
public long countSizeOfFiles() {
java.io.File file = new java.io.File(path);
if (!file.exists()) return 0;
return `
}
}
public class Directory extends FileSystemNode {
private List<FileSystemNode> subNodes = new ArrayList<>();
public Directory(String path) {
super(path);
}
@Override
public int countNumOfFiles() {
int numOfFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
numOfFiles += fileOrDir.countNumOfFiles();
}
return numOfFiles;
}
@Override
public long countSizeOfFiles() {
long sizeofFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
sizeofFiles += fileOrDir.countSizeOfFiles();
}
return sizeofFiles;
}
public void addSubNode(FileSystemNode fileOrDir) {
subNodes.add(fileOrDir);
}
public void removeSubNode(FileSystemNode fileOrDir) {
int size = subNodes.size();
int i = 0;
for (; i < size; ++i) {
if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
break;
}
}
if (i < size) {
subNodes.remove(i);
}
}
}
文件和目錄類都設(shè)計(jì)好了,我們來(lái)看,如何用它們來(lái)表示一個(gè)文件系統(tǒng)中的目錄樹結(jié)構(gòu)。
具體的代碼示例如下所示:
public class Demo {
public static void main(String[] args) {
Directory fileSystemTree = new Directory("/");
Directory nodeYdlclass = new Directory("/ydlclass/");
Directory nodeYdl = new Directory("/ydl/");
fileSystemTree.addSubNode(nodeYdlclass);
fileSystemTree.addSubNode(nodeYdl);
File nodeYdlclassA = new File("/ydlclass/a.txt");
File nodeYdlclassB = new File("/ydlclass/b.txt");
Directory nodeYdlclassMovies = new Directory("/ydlclass/movies/");
nodeYdlclass.addSubNode(nodeYdlclassA);
nodeYdlclass.addSubNode(nodeYdlclassB);
nodeYdlclass.addSubNode(nodeYdlclassMovies);
File nodeYdlclassMoviesC = new File("/ydlclass/movies/c.avi");
nodeYdlclassMovies.addSubNode(nodeYdlclassMoviesC);
System.out.println("/ files num:" + fileSystemTree.countNumOfFiles());
System.out.println("/wz/ files num:" + node_wz.countNumOfFiles());
}
}
我們對(duì)照著這個(gè)例子,再重新看一下組合模式的定義:“將一組對(duì)象(文件和目錄)組織成樹形結(jié)構(gòu),以表示一種‘部分 - 整體’的層次結(jié)構(gòu)(目錄與子目錄的嵌套結(jié)構(gòu))。組合模式讓客戶端可以統(tǒng)一單個(gè)對(duì)象(文件)和組合對(duì)象(目錄)的處理邏輯(遞歸遍歷)?!?/p>
實(shí)際上,剛才講的這種組合模式的設(shè)計(jì)思路,與其說是一種設(shè)計(jì)模式,倒不如說是對(duì)業(yè)務(wù)場(chǎng)景的一種數(shù)據(jù)結(jié)構(gòu)和算法的抽象。其中,數(shù)據(jù)可以表示成樹這種數(shù)據(jù)結(jié)構(gòu),業(yè)務(wù)需求可以通過在樹上的遞歸遍歷算法來(lái)實(shí)現(xiàn)。
案例二
假設(shè)我們?cè)陂_發(fā)一個(gè) OA 系統(tǒng)(辦公自動(dòng)化系統(tǒng))。公司的組織結(jié)構(gòu)包含部門和員工兩種數(shù)據(jù)類型。其中,部門又可以包含子部門和員工。在數(shù)據(jù)庫(kù)中的表結(jié)構(gòu)如下所示:
我們希望在內(nèi)存中構(gòu)建整個(gè)公司的人員架構(gòu)圖(部門、子部門、員工的隸屬關(guān)系),并且提供接口計(jì)算出部門的薪資成本(隸屬于這個(gè)部門的所有員工的薪資和)。
部門包含子部門和員工,這是一種嵌套結(jié)構(gòu),可以表示成樹這種數(shù)據(jù)結(jié)構(gòu)。計(jì)算每個(gè)部門的薪資開支這樣一個(gè)需求,也可以通過在樹上的遍歷算法來(lái)實(shí)現(xiàn)。所以,從這個(gè)角度來(lái)看,這個(gè)應(yīng)用場(chǎng)景可以使用組合模式來(lái)設(shè)計(jì)和實(shí)現(xiàn)。
這個(gè)例子的代碼結(jié)構(gòu)跟上一個(gè)例子的很相似,代碼實(shí)現(xiàn)我直接貼在了下面,你可以對(duì)比著看一下。其中,HumanResource 是部門類(Department)和員工類(Employee)抽象出來(lái)的父類,為的是能統(tǒng)一薪資的處理邏輯。Demo 中的代碼負(fù)責(zé)從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)并在內(nèi)存中構(gòu)建組織架構(gòu)圖。
public abstract class HumanResource {
protected long id;
protected double salary;
public HumanResource(long id) {
this.id = id;
}
public long getId() {
return id;
}
public abstract double calculateSalary();
}
public class Employee extends HumanResource {
public Employee(long id, double salary) {
super(id);
this.salary = salary;
}
@Override
public double calculateSalary() {
return salary;
}
}
public class Department extends HumanResource {
private List<HumanResource> subNodes = new ArrayList<>();
public Department(long id) {
super(id);
}
@Override
public double calculateSalary() {
double totalSalary = 0;
for (HumanResource hr : subNodes) {
totalSalary += hr.calculateSalary();
}
this.salary = totalSalary;
return totalSalary;
}
public void addSubNode(HumanResource hr) {
subNodes.add(hr);
}
}
// 構(gòu)建組織架構(gòu)的代碼
public class Demo {
private static final long ORGANIZATION_ROOT_ID = 1001;
private DepartmentRepo departmentRepo; // 依賴注入
private EmployeeRepo employeeRepo; // 依賴注入
public void buildOrganization() {
Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
buildOrganization(rootDepartment);
}
private void buildOrganization(Department department) {
List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department.getId());
for (Long subDepartmentId : subDepartmentIds) {
Department subDepartment = new Department(subDepartmentId);
department.addSubNode(subDepartment);
buildOrganization(subDepartment);
}
List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId());
for (Long employeeId : employeeIds) {
double salary = employeeRepo.getEmployeeSalary(employeeId);
department.addSubNode(new Employee(employeeId, salary));
}
}
}
將一組對(duì)象(員工和部門)組織成樹形結(jié)構(gòu),以表示一種‘部分 - 整體’的層次結(jié)構(gòu)(部門與子部門的嵌套結(jié)構(gòu))。組合模式讓客戶端可以統(tǒng)一單個(gè)對(duì)象(員工)和組合對(duì)象(部門)的處理邏輯(遞歸遍歷)?!?/p>
二、組合模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 可以使用相同的方式來(lái)處理單個(gè)對(duì)象和組合對(duì)象,客戶端無(wú)需知道對(duì)象的具體類型。
- 可以方便地增加新的組合對(duì)象或葉子對(duì)象,同時(shí)也可以方便地對(duì)組合對(duì)象進(jìn)行遍歷和操作。
- 可以使代碼更加簡(jiǎn)潔和易于維護(hù),因?yàn)槭褂媒M合模式可以避免大量的 if-else 或 switch-case 語(yǔ)句。
缺點(diǎn)
- 在組合對(duì)象中,可能會(huì)包含大量的葉子對(duì)象,這可能會(huì)導(dǎo)致系統(tǒng)的性能下降。
- 可能會(huì)使設(shè)計(jì)過于抽象化,使得代碼難以理解和維護(hù)。
總之,組合模式在處理樹形結(jié)構(gòu)等層次結(jié)構(gòu)時(shí)非常有用,可以方便地處理單個(gè)對(duì)象和組合對(duì)象,使得代碼更加簡(jiǎn)潔和易于維護(hù)。
三、重點(diǎn)回顧
組合模式的設(shè)計(jì)思路,與其說是一種設(shè)計(jì)模式,倒不如說是對(duì)業(yè)務(wù)場(chǎng)景的一種數(shù)據(jù)結(jié)構(gòu)和算法的抽象。其中,數(shù)據(jù)可以表示成樹這種數(shù)據(jù)結(jié)構(gòu),業(yè)務(wù)需求可以通過在樹上的遞歸遍歷算法來(lái)實(shí)現(xiàn)。
組合模式,將一組對(duì)象組織成樹形結(jié)構(gòu),將單個(gè)對(duì)象和組合對(duì)象都看做樹中的節(jié)點(diǎn),以統(tǒng)一處理邏輯,并且它利用樹形結(jié)構(gòu)的特點(diǎn),遞歸地處理每個(gè)子樹,依次簡(jiǎn)化代碼實(shí)現(xiàn)。使用組合模式的前提在于,你的業(yè)務(wù)場(chǎng)景必須能夠表示成樹形結(jié)構(gòu)。所以,組合模式的應(yīng)用場(chǎng)景也比較局限,它并不是一種很常用的設(shè)計(jì)模式。
四、源碼應(yīng)用
1、JDK源碼
組合模式在 JDK 源碼中也有很多應(yīng)用。以下是一些常見的使用場(chǎng)景:
- Java Collection 框架:在 Java Collection 框架中,Collection 接口就是一個(gè)抽象構(gòu)件,它定義了集合對(duì)象的通用接口。List、Set 和 Map 等具體集合類就是組合節(jié)點(diǎn)或葉子節(jié)點(diǎn),用于存儲(chǔ)和操作集合中的元素。
- Servlet API:在 Servlet API 中,ServletRequest 和 ServletResponse 接口就是一個(gè)抽象構(gòu)件,它定義了 Servlet 的通用接口。HttpServletRequest 和 HttpServletResponse 等具體類就是組合節(jié)點(diǎn)或葉子節(jié)點(diǎn),用于處理 Web 請(qǐng)求和響應(yīng)。
總之,組合模式在 JDK 源碼中也有著廣泛的應(yīng)用,可以幫助開發(fā)者更加方便地操作各種層次結(jié)構(gòu)。
2、SSM源碼
在 SSM(Spring + Spring MVC + MyBatis)框架中,組合模式也有一些應(yīng)用場(chǎng)景,以下是一些常見的使用場(chǎng)景:
- Spring MVC:在 Spring MVC 中,Controller 就是一個(gè)組合節(jié)點(diǎn),它可以包含其他組合對(duì)象或葉子對(duì)象,用于處理 Web 請(qǐng)求和響應(yīng)。對(duì)于復(fù)雜的請(qǐng)求處理邏輯,可以將一個(gè) Controller 分解成多個(gè)子 Controller,然后通過組合的方式將它們組合起來(lái),使得請(qǐng)求處理邏輯更加清晰和易于維護(hù)。
- MyBatis:在 MyBatis 中,SqlNode 就是一個(gè)抽象構(gòu)件,它定義了 SQL 節(jié)點(diǎn)的通用接口。WhereSqlNode、ChooseSqlNode、IfSqlNode 等具體類就是組合節(jié)點(diǎn)或葉子節(jié)點(diǎn),用于構(gòu)建 SQL 語(yǔ)句,解析動(dòng)態(tài)sql。
總之,組合模式可以幫助開發(fā)者更加方便地管理和組織各種組件和模塊。
文章到這里就結(jié)束了,如果有什么疑問的地方,可以在評(píng)論區(qū)指出~
希望能和大佬們一起努力,諸君頂峰相見文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-708161.html
再次感謝各位小伙伴兒們的支持!?。?span toymoban-style="hidden">文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-708161.html
到了這里,關(guān)于【23種設(shè)計(jì)模式】組合模式【?】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!