一文徹底搞明白組合模式
本篇講解Java設(shè)計(jì)模式中的組合模式,分為定義、模式應(yīng)用前案例、結(jié)構(gòu)、模式應(yīng)用后案例、適用場(chǎng)景、模式可能存在的困惑和本質(zhì)探討7個(gè)部分。
定義
組合模式是將對(duì)象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)。組合模式使得客戶對(duì)單個(gè)對(duì)象和復(fù)合對(duì)象的使用具有一致性。
在新的分類方式中,組合模式被劃分至類之間的交互類別中,其簡(jiǎn)化的是調(diào)用方與具備樹結(jié)構(gòu)的一組對(duì)象之間的交互,具體通過一致性的行為實(shí)現(xiàn)。
模式應(yīng)用前案例
下面以一個(gè)典型的文件和目錄為例來進(jìn)行說明,先來看一下未應(yīng)用組合模式之前的代碼實(shí)現(xiàn)。
public class File {//文件結(jié)構(gòu)
private final String name;
public File(String name) {
this.name = name;
}
public void display() {
System.out.println("File: " + this.name);
}
}
public class Directory {//目錄結(jié)構(gòu)
private String name;
private final List<File> files;
private final List<Directory> directories;
// 初始化方法
public Directory(String name){
this.name = this.name;
this.files = new ArrayList<>();
this.directories = new ArrayList<>();
}
// 添加子節(jié)點(diǎn)
public void addFile(File file){
this.files.add(file);
}
// 添加子目錄
public void addDirectory(Directory directory) {
this.directories.add(directory);
}
public void display(){
//System.out.println("Directory:"+this.name);
for(File file : this.files){
file.display();
}
for (Directory dir : this.directories) {
dir.display();
}
}
}
public class Client {//調(diào)用方代碼
public static void main(String[] ars){
Directory root= new Directory("Root");
File file1=new File("file1.txt");
File file2=new File("file2.txt");
root.addFile(file1);
root.addFile(file2);
Directory subDirecory =new Directory ("Subdirectory");
File file3 = new File("file3.tx");
File file4 = new File("file4.tx");
subDirecory.addFile(file3);
subDirecory.addFile(file4);
root.addDirectory(subDirecory);
root.display();
}
}
我們知道,文件和目錄兩者是一個(gè)大的樹結(jié)構(gòu)中的節(jié)點(diǎn)。在上面未使用組合模式的代碼中,文件和目錄都有自己定義的方法。這樣在構(gòu)建一個(gè)多層樹結(jié)構(gòu)的過程中,復(fù)雜度會(huì)提升。
結(jié)構(gòu)
組合模式的示例代碼如下。
public interface Component {
void operation();
void add(Component component);
void remove(Component component);
Component Display(int index);
}
public class Leaf implements Component{
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Leaf: " + name + " operation()");
}
@Override
public void add(Component component) {
throw new UnsupportedOperationException("Leaf cannot have children");
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException("Leaf cannot remove children");
}
@Override
public Component Display(int index) {
throw new UnsupportedOperationException("Leaf cannot get child");
}
}
public interface Component {
void operation();
void add(Component component);
void remove(Component component);
Component Display(int index);
}
public class Client {
public static void main(String[] args) {
// 創(chuàng)建葉子節(jié)點(diǎn)
Component leaf1 = new Leaf("LeafA");
Component leaf2 = new Leaf("LeafB");
Component leaf3 = new Leaf("LeafC");
// 創(chuàng)建復(fù)合節(jié)點(diǎn)
Component composite = new Composite("CompositeX");
composite.add(leaf1);
composite.add(leaf2);
// 創(chuàng)建另一個(gè)復(fù)合節(jié)點(diǎn),并添加之前的復(fù)合節(jié)點(diǎn)和新的葉子節(jié)點(diǎn)
Component root = new Composite("Root");
root.add(composite);
root.add(leaf3);
// 執(zhí)行操作
root.operation();
}
}
模式應(yīng)用后案例
上面文件與目錄的案例,使用組合模式之后的代碼實(shí)現(xiàn)如下。
public interface IComponent {//接口
void display();
}
public class File implements IComponent{//文件實(shí)現(xiàn)
private final String name;
public File(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("File: " + this.name);
}
}
public class Directory implements IComponent{//目錄實(shí)現(xiàn)
private String name;
private final List<IComponent> children;
// 初始化方法
public Directory(String name){
this.name = this.name;
this.children = new ArrayList<>();
}
// 添加子節(jié)點(diǎn)
public void addComponent(IComponent component){
this.children.add(component);
}
// 顯示目錄內(nèi)容
@Override
public void display() {
//System.out.println("Directory: " + this.name);
for (IComponent child : this.children) {
child.display();
}
}
}
public class Client {//調(diào)用方代碼
public static void main(String[] ars){
Directory root= new Directory("Root");
File file1 = new File("file1.txt");
File file2 = new File ("file2.txt");
root.addComponent(file1);
root.addComponent(file2);
Directory subDirectory =new Directory ("Subdirectory");
File file3 = new File("file3.txt");
File file4 = new File("file4.txt");
subDirectory.addComponent(file3);
subDirectory.addComponent(file4);
root.addComponent(subDirectory);
root.display();
}
}
在上述代碼中,由于樹的結(jié)構(gòu)使用一個(gè)接口和實(shí)現(xiàn)的家族來實(shí)現(xiàn),這樣樹的結(jié)構(gòu)中所有類的行為都是一致的,簡(jiǎn)化了編碼時(shí)的復(fù)雜度。
適用場(chǎng)景
當(dāng)需求中出現(xiàn)的一系列概念或?qū)ο?,它們之間存在部分-整體的層次結(jié)構(gòu)或共同構(gòu)成一顆樹的結(jié)構(gòu)時(shí),就可以考慮使用組合模式。
模式可能存在的困惑
困惑1:組合模式中的“組合”,與“組合優(yōu)于繼承”中的“組合”,有什么關(guān)聯(lián)?
兩者都代表了一種關(guān)系。前者的“組合”指的是將一系列對(duì)象按照層次化結(jié)構(gòu)進(jìn)行組織。而后者的“組合”指的是兩個(gè)對(duì)象之間的聚合或組合關(guān)系,以此來取代類之間繼承關(guān)系。
本質(zhì)
組合模式的本質(zhì)在于提供了一種機(jī)制來處理對(duì)象之間的部分-整體關(guān)系,并且通過統(tǒng)一接口來簡(jiǎn)化調(diào)用方使用復(fù)雜層次結(jié)構(gòu)時(shí)可能遇到的問題。