更新時(shí)間:2022-10-11 10:10:49 來源:動(dòng)力節(jié)點(diǎn) 瀏覽992次
設(shè)計(jì)模式中的組合模式是什么?小編來告訴大家。將一組對象組織(Compose)成樹形結(jié)構(gòu),以表示一種“部分 - 整體”的層次結(jié)構(gòu)。組合讓客戶端可以統(tǒng)一單個(gè)對象和組合對象的處理邏輯。接下來,對于組合模式,舉個(gè)例子來給你解釋一下。
假設(shè)我們有這樣一個(gè)需求:設(shè)計(jì)一個(gè)類來表示文件系統(tǒng)中的目錄,能方便地實(shí)現(xiàn)下面這些功能:
動(dòng)態(tài)地添加、刪除某個(gè)目錄下的子目錄或文件;
統(tǒng)計(jì)指定目錄下的文件個(gè)數(shù);
統(tǒng)計(jì)指定目錄下的文件總大小。
這里給出了這個(gè)類的骨架代碼,如下所示。其中的核心邏輯并未實(shí)現(xiàn),你可以試著自己去補(bǔ)充完整,再來看我的講解。在下面的代碼實(shí)現(xiàn)中,我們把文件和目錄統(tǒng)一用 FileSystemNode 類來表示,并且通過File類屬性來區(qū)分。
public class FileSystemNode {
private String path;
private boolean isFile;
private List<FileSystemNode> subNodes = new ArrayList<>();
public FileSystemNode(String path, boolean isFile) {
this.path = path;
this.isFile = isFile;
}
public int countNumOfFiles() {
// TODO:...
}
public long countSizeOfFiles() {
// TODO:...
}
public String getPath() {
return path;
}
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);
}
}
}
想要補(bǔ)全其中的 countNumOfFiles() 和 countSizeOfFiles() 這兩個(gè)函數(shù),并不是件難事,實(shí)際上這就是樹上的遞歸遍歷算法。對于文件,我們直接返回文件的個(gè)數(shù)(返回 1)或大小。對于目錄,我們遍歷目錄中每個(gè)子目錄或者文件,遞歸計(jì)算它們的個(gè)數(shù)或大小,然后求和,就是這個(gè)目錄下的文件個(gè)數(shù)和文件大小。
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;
單純從功能實(shí)現(xiàn)角度來說,上面的代碼沒有問題,已經(jīng)實(shí)現(xiàn)了我們想要的功能。但是,如果我們開發(fā)的是一個(gè)大型系統(tǒng),從擴(kuò)展性(文件或目錄可能會(huì)對應(yīng)不同的操作)、業(yè)務(wù)建模(文件和目錄從業(yè)務(wù)上是兩個(gè)概念)、代碼的可讀性(文件和目錄區(qū)分對待更加符合人們對業(yè)務(wù)的認(rèn)知)的角度來說,我們最好對文件和目錄進(jìn)行區(qū)分設(shè)計(jì),定義為 File 和 Directory 兩個(gè)類。
按照這個(gè)設(shè)計(jì)思路,我們對代碼進(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 file.length();
}
}
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ì)好了,我們來看,如何用它們來表示一個(gè)文件系統(tǒng)中的目錄樹結(jié)構(gòu)。具體的代碼示例如下所示:
public class Demo {
public static void main(String[] args) {
/**
* /
*
* <p>/wz/
*
* <p>/wz/a.txt
*
* <p>/wz/b.txt
*
* <p>/wz/movies/
*
* <p>/wz/movies/c.avi
*
* <p>/xzg/
*
* <p>/xzg/docs/
*
* <p>/xzg/docs/d.txt
*/
Directory fileSystemTree = new Directory("/");
Directory node_wz = new Directory("/wz/");
Directory node_xzg = new Directory("/xzg/");
fileSystemTree.addSubNode(node_wz);
fileSystemTree.addSubNode(node_xzg);
File node_wz_a = new File("/wz/a.txt");
File node_wz_b = new File("/wz/b.txt");
Directory node_wz_movies = new Directory("/wz/movies/");
node_wz.addSubNode(node_wz_a);
node_wz.addSubNode(node_wz_b);
node_wz.addSubNode(node_wz_movies);
File node_wz_movies_c = new File("/wz/movies/c.avi");
node_wz_movies.addSubNode(node_wz_movies_c);
Directory node_xzg_docs = new Directory("/xzg/docs/");
node_xzg.addSubNode(node_xzg_docs);
File node_xzg_docs_d = new File("/xzg/docs/d.txt");
node_xzg_docs.addSubNode(node_xzg_docs_d);
System.out.println("/ files num:" + fileSystemTree.countNumOfFiles());
System.out.println("/wz/ files num:" + node_wz.countNumOfFiles());
}
}
我們對照著這個(gè)例子,再重新看一下組合模式的定義:“將一組對象(文件和目錄)組織成樹形結(jié)構(gòu),以表示一種‘部分 - 整體’的層次結(jié)構(gòu)(目錄與子目錄的嵌套結(jié)構(gòu))。組合模式讓客戶端可以統(tǒng)一單個(gè)對象(文件)和組合對象(目錄)的處理邏輯(遞歸遍歷)。”
實(shí)際上,剛才講的這種組合模式的設(shè)計(jì)思路,與其說是一種設(shè)計(jì)模式,倒不如說是對業(yè)務(wù)場景的一種數(shù)據(jù)結(jié)構(gòu)和算法的抽象。其中,數(shù)據(jù)可以表示成樹這種數(shù)據(jù)結(jié)構(gòu),業(yè)務(wù)需求可以通過在樹上的遞歸遍歷算法來實(shí)現(xiàn)。
剛剛我們講了文件系統(tǒng)的例子,對于組合模式,我這里再舉一個(gè)例子。搞懂了這兩個(gè)例子,你基本上就算掌握了組合模式。在實(shí)際的項(xiàng)目中,遇到類似的可以表示成樹形結(jié)構(gòu)的業(yè)務(wù)場景,你只要“照葫蘆畫瓢”去設(shè)計(jì)就可以了。
假設(shè)我們在開發(fā)一個(gè) OA 系統(tǒng)(辦公自動(dòng)化系統(tǒng))。公司的組織結(jié)構(gòu)包含部門和員工兩種數(shù)據(jù)類型。其中,部門又可以包含子部門和員工。在數(shù)據(jù)庫中的表結(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è)需求,也可以通過在樹上的遍歷算法來實(shí)現(xiàn)。所以,從這個(gè)角度來看,這個(gè)應(yīng)用場景可以使用組合模式來設(shè)計(jì)和實(shí)現(xiàn)。
這個(gè)例子的代碼結(jié)構(gòu)跟上一個(gè)例子的很相似,代碼實(shí)現(xiàn)我直接貼在了下面,你可以對比著看一下。其中,HumanResource 是部門類(Department)和員工類(Employee)抽象出來的父類,為的是能統(tǒng)一薪資的處理邏輯。Demo 中的代碼負(fù)責(zé)從數(shù)據(jù)庫中讀取數(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));
}
}
}
我們再拿組合模式的定義跟這個(gè)例子對照一下:“將一組對象(員工和部門)組織成樹形結(jié)構(gòu),以表示一種‘部分 - 整體’的層次結(jié)構(gòu)(部門與子部門的嵌套結(jié)構(gòu))。組合模式讓客戶端可以統(tǒng)一單個(gè)對象(員工)和組合對象(部門)的處理邏輯(遞歸遍歷)。
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743