組合模式(Composite)是針對由多個節點物件(部分)組成的樹形結構的物件(整體)而發展出的一種結構型設計模式,它能夠使使用者端在操作整體物件或者其下的每個節點物件時做出統一的響應,保證樹形結構物件使用方法的一致性,使使用者端不必關注物件的整體或部分,最終達到物件複雜的層次結構與使用者端解耦的目的。
組合模式的核心思想是將物件看作是一個樹形結構,其中每個節點可以是一個單獨的物件(葉子節點)或者一個包含其他節點的容器(組合節點)。葉子節點和組合節點都實現了相同的介面,這樣使用者端就可以對它們進行一致的操作,而不需要關心它們的具體型別。
組合模式有以下幾個角色:
組合模式的好處有:
組合模式的壞處有:
組合模式是一種將物件組合成樹形結構的設計模式,它可以表示整體-部分的層次關係,並且提供了一致的介面來操作單個物件和物件組合。應用場景有:
假設我們有一個檔案系統,其中有兩種型別的檔案:文字檔案和資料夾。文字檔案是葉子節點,資料夾是組合節點,可以包含其他檔案。我們想要使用組合模式來實現檔案系統的層次結構,並且提供一個列印檔案路徑的方法。程式碼如下:
定義抽象元件
public interface File {
// 獲取檔名稱
String getName();
// 新增子檔案
void add(File file);
// 刪除子檔案
void remove(File file);
// 獲取子檔案
List<File> getChildren();
// 列印檔案路徑
void printPath(int space);
}
定義葉子節點
public class TextFile implements File {
private String name;
public TextFile(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void add(File file) {
throw new UnsupportedOperationException("Text file cannot add child file");
}
@Override
public void remove(File file) {
throw new UnsupportedOperationException("Text file cannot remove child file");
}
@Override
public List<File> getChildren() {
throw new UnsupportedOperationException("Text file has no child file");
}
@Override
public void printPath(int space) {
StringBuilder sp = new StringBuilder();
for (int i = 0; i < space; i++) {
sp.append(" ");
}
System.out.println(sp + name);
}
}
定義組合節點
public class Folder implements File {
private String name;
private List<File> children;
public Folder(String name) {
this.name = name;
children = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
@Override
public void add(File file) {
children.add(file);
}
@Override
public void remove(File file) {
children.remove(file);
}
@Override
public List<File> getChildren() {
return children;
}
@Override
public void printPath(int space) {
StringBuilder sp = new StringBuilder();
for (int i = 0; i < space; i++) {
sp.append(" ");
}
System.out.println(sp + name);
space += 2;
for (File child : children) {
child.printPath(space);
}
}
}
使用者端程式碼
public class Client {
public static void main(String[] args) {
// 建立一個根資料夾,並新增兩個文字檔案和一個子資料夾
File root = new Folder("root");
root.add(new TextFile("a.txt"));
root.add(new TextFile("b.txt"));
File subFolder = new Folder("subFolder");
root.add(subFolder);
// 在子資料夾中新增兩個文字檔案
subFolder.add(new TextFile("c.txt"));
subFolder.add(new TextFile("d.txt"));
// 列印根資料夾的路徑
root.printPath(0);
}
}
輸出結果:
root
a.txt
b.txt
subFolder
c.txt
d.txt
package main
// importing fmt package
import (
"fmt"
)
// IComposite interface
type IComposite interface {
perform()
}
// Leaflet struct
type Leaflet struct {
name string
}
// Leaflet class method perform
func (leaf *Leaflet) perform() {
fmt.Println("Leaflet " + leaf.name)
}
// Branch struct
type Branch struct {
leafs []Leaflet
name string
branches []Branch
}
// Branch class method perform
func (branch *Branch) perform() {
fmt.Println("Branch: " + branch.name)
for _, leaf := range branch.leafs {
leaf.perform()
}
for _, branch := range branch.branches {
branch.perform()
}
}
// Branch class method add leaflet
func (branch *Branch) add(leaf Leaflet) {
branch.leafs = append(branch.leafs, leaf)
}
//Branch class method addBranch branch
func (branch *Branch) addBranch(newBranch Branch) {
branch.branches = append(branch.branches, newBranch)
}
//Branch class method getLeaflets
func (branch *Branch) getLeaflets() []Leaflet {
return branch.leafs
}
// main method
func main() {
var branch = &Branch{name: "branch 1"}
var leaf1 = Leaflet{name: "leaf 1"}
var leaf2 = Leaflet{name: "leaf 2"}
var branch2 = Branch{name: "branch 2"}
branch.add(leaf1)
branch.add(leaf2)
branch.addBranch(branch2)
branch.perform()
}
輸出結果:
G:\GoLang\examples>go run composite.go
Branch: branch 1
Leaflet leaf 1
Leaflet leaf 2
Branch: branch 2
Spring 框架也可以使用組合模式來實現物件的層次結構,它提供了一個註解叫做 @Component
,它可以用來標註一個類是一個元件,即一個可被Spring管理的Bean物件。@Component
註解有一個屬性叫做value,它可以用來指定元件的名稱。我們可以使用 @Component
註解來標註我們的檔案類,然後在組態檔或註解中宣告這些元件,Spring 就會自動建立和管理這些元件物件。
假設我們有一個檔案系統,其中有兩種型別的檔案:文字檔案和資料夾。文字檔案是葉子節點,資料夾是組合節點,可以包含其他檔案。我們想要使用組合模式來實現檔案系統的層次結構,並且提供一個列印檔案路徑的方法。我們可以使用 @Component
註解來實現,程式碼如下:
抽象元件不用改造
public interface File {
// 獲取檔名稱
String getName();
// 新增子檔案
void add(File file);
// 刪除子檔案
void remove(File file);
// 獲取子檔案
List<File> getChildren();
// 列印檔案路徑
void printPath();
}
葉子節點新增 @Component("a.txt")
註解
@Component("a.txt")
public class TextFile implements File {
private String name;
public TextFile() {
this.name = "a.txt";
}
@Override
public String getName() {
return name;
}
@Override
public void add(File file) {
throw new UnsupportedOperationException("Text file cannot add child file");
}
@Override
public void remove(File file) {
throw new UnsupportedOperationException("Text file cannot remove child file");
}
@Override
public List<File> getChildren() {
throw new UnsupportedOperationException("Text file has no child file");
}
@Override
public void printPath() {
System.out.println(name);
}
}
組合節點新增 @Component("root")
註解
@Component("root")
public class Folder implements File {
private String name;
private List<File> children;
// 通過@Autowired註解自動注入所有型別為File的Bean物件到children集合中
@Autowired
public Folder(List<File> children) {
this.name = "root";
this.children = children;
}
@Override
public String getName() {
return name;
}
@Override
public void add(File file) {
children.add(file);
}
@Override
public void remove(File file) {
children.remove(file);
}
@Override
public List<File> getChildren() {
return children;
}
@Override
public void printPath() {
System.out.println(name);
for (File child : children) {
child.printPath();
}
}
}
SpringBoot 測試程式碼
@Slf4j
@SpringBootTest
class SpringBootTest {
@Autowired
private Folder folder;
@Test
void test() {
folder.printPath();
}
}
輸出結果:
root
a.txt
組合模式是一種常用的結構型設計模式,它可以將物件組合成樹形結構,並且提供了一致的介面來操作單個物件和物件組合,是一種值得學習和掌握的設計模式。
關注公眾號【waynblog】每週分享技術乾貨、開源專案、實戰經驗、高效開發工具等,您的關注將是我的更新動力!