設計模式詳解——組合模式
前言
今天我們分享的這個設計模式,用一句話來概括的話,就是化零為整,再進一步解釋就是,通過這個設計模式,我們可以像操作一個物件一樣操作一個物件的集合,不過這個物件在組合模式中被稱作葉節點,而物件的集合被稱為組合,而這個結合本身也是也節點的樹形結構的集合。是不是感覺越來越繞了呢?沒關係,下面我就來詳細看下組合模式的基本原理和具體實現。
組合模式
組合模式允許我們將物件組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及物件組合。
它在我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以像處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。
使用場景
-
想表示物件的部分-整體層次結構(樹形結構)。
-
希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。
要點
- 組合模式讓我們能用樹形方式建立物件的結構,樹裡面包含了組合以及個別的物件
- 使用組合模式,我們能把相同的操作應用到組合和個別物件上。換句話說,在大多數情況下,我們可以忽略物件組合和個別物件之間的差別。
示例
下面我們通過一個具體例項來演示下組合模式到底是如何工作的。這裡我們直接用了《Head First
設計模式》上的示例,是對餐廳選單的模擬,只不過我引入了一個通用介面。
組合介面
首先是組合的介面,這個介面不論是葉節點還是葉節點組合都需要繼承,不過都不是直接繼承。
public interface Component {
void add(Component component);
void remove(Component component);
Component getChild(int i);
}
組合抽象類
這個抽象類就是給葉節點和節點組合繼承的,其中方法都有了預設實現,預設都丟擲了UnsupportedOperationException
public abstract class MenuComponent implements Component { @Override public void add(Component component) { throw new UnsupportedOperationException(); } @Override public void remove(Component component) { throw new UnsupportedOperationException(); } @Override public Component getChild(int i) { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public double getPrice() { throw new UnsupportedOperationException(); } public boolean isVegetarian() { throw new UnsupportedOperationException(); } public void print() { throw new UnsupportedOperationException(); } }
葉節點實現
這裡的葉節點主要覆寫了父類的getName
、getDescription
、isVegetarian
和getPrice
等方法,這些方法也主要是針對具體選單的
public class MenuItem extends MenuComponent {
private String name;
private String description;
private boolean vegetarian;
private double price;
public MenuItem(String name, String description, boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isVegetarian() {
return vegetarian;
}
@Override
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.println("==========start=============");
System.out.printf("name: %s price: ¥%s%n", this.getName(), this.getPrice());
System.out.printf("description: %s isVegetarian: %s%n", this.getDescription(), this.isVegetarian());
System.out.println("==========end=============");
}
}
節點組合實現
因為節點組合要管理葉節點,所以這裡主要實現了add
、remove
、getChild
等方法,當然也實現了getName
和getDescription
等基礎方法:
public class Menu extends MenuComponent {
ArrayList<Component> menuComponents = new ArrayList<>();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public void add(Component component) {
menuComponents.add(component);
}
@Override
public void remove(Component component) {
menuComponents.remove(component);
}
@Override
public Component getChild(int i) {
return menuComponents.get(i);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDescription() {
return super.getDescription();
}
@Override
public void print() {
System.out.println("==========start=============");
System.out.printf("name: %s", this.getName());
System.out.printf("description: %s", this.getDescription());
System.out.println("==========child start=============");
Iterator<Component> iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent component = (MenuComponent)iterator.next();
component.print();
}
System.out.println("==========child end=============");
System.out.println("============end===========");
}
}
測試程式碼
下面我們開始編寫測試程式碼。這裡我們分別構造了多個葉節點,並將其中一部分組成節點組合,最後分別執行葉節點和子節點的print
方法(這裡的print
方法其實就是我們設計模式原理圖中的operation
方法)
@Test
public void testComponent() {
// 葉節點
MenuItem slr = new MenuItem("燒鹿茸", "好吃美味,價格實惠", Boolean.FALSE, 180.0);
MenuItem sxz = new MenuItem("燒熊掌", "好吃美味,價格實惠", Boolean.FALSE, 190.0);
MenuItem hsr = new MenuItem("紅燒肉", "好吃美味,價格實惠", Boolean.FALSE, 36.0);
MenuItem hsqz = new MenuItem("紅燒茄子", "好吃美味,價格實惠", Boolean.TRUE, 14.0);
MenuItem hsjk = new MenuItem("紅燒雞塊", "好吃美味,價格實惠", Boolean.FALSE, 38.0);
MenuItem yxrs = new MenuItem("魚香肉絲", "好吃美味,價格實惠", Boolean.FALSE, 22.0);
MenuItem ssbc = new MenuItem("手撕包菜", "好吃美味,價格實惠", Boolean.TRUE, 12.0);
// 組合節點
Menu menu = new Menu("家常菜", "美味家常菜");
menu.add(hsr);
menu.add(hsqz);
menu.add(hsjk);
menu.add(yxrs);
menu.add(ssbc);
// 子節點print方法
slr.print();
sxz.print();
// 組合節點print方法
menu.print();
}
執行結果
總結
想必通過上面的示例,各位小夥伴已經對組合模式有了一定的認知,對這種設計模式適用的場景也有了比較明確認識:如果在一個業務中,單個物件和物件的集合需要具備同樣的型別和方法,那組合模式就是最佳選擇,比如我們這裡的選單和選單組合,當然,更具體的應用場景,還需要各位小夥伴結合具體應用場景分析,但是學習的時候多思考應用場景才是學習正在的目的。好了,今天就到這裡吧!