1. 程式人生 > >09 組合模式(Composite Pattern)

09 組合模式(Composite Pattern)

本篇博文說的是組合模式

描述性文字

組合模式,又稱為 部分整體模式,把具有相似的一組物件 當做一個物件處理,用一種樹狀的結構來組合物件,再提供統一的方法去訪問相似的物件,以此忽略掉物件與物件容器間的差別。

舉個栗子

注:此處直接使用的是原圖。

假設這兩類需求如下:

選單:選單名,描述資訊,新增,新增刪除子選單或菜品 遞迴打印出所有的子選單與菜品!

菜品:菜名,描述資訊,價格,列印資訊好的,先試試不用組合模式,要怎麼寫~

不使用組合模式寫選單

示例程式碼:

package structPattrn.compositePattern;

import java.util.ArrayList;
import java.util.List;

/**
 * 組合模式測試例程
 * @Package       structPattrn.compositePattern
 * @Title:        CompositePatternDemo.java
 * @Company:      $
 * @author        BurgessLee 
 * @date          2018年10月25日-下午5:06:05
 * @Description:  $
 */
public class CompositePatternDemo {
	
	public static void main(String[] args) {
		//不用組合模式測試例程
		Menu menu = new Menu("大選單","包含所有的子選單");
		Menu drinkMenu = new Menu("飲品選單","都是喝的");
		Menu eatMenu = new Menu("小吃選單","都是吃的");
		
		MilkTea milkTea = new MilkTea("珍珠賣茶","珍珠+奶茶", 5);
		Juice juice = new Juice("獼猴桃飲料","無新增劑",5);
		HandCake handCake = new HandCake("咖哩魚蛋","微辣",6);
		FishBoll fishBoll = new FishBoll("培根手抓餅","正宗臺灣風味",6);
		
		drinkMenu.addMilkTea(milkTea);
		drinkMenu.addJuice(juice);
		eatMenu.addHandCake(handCake);
		eatMenu.addFishBoll(fishBoll);
		menu.addMenu(drinkMenu);
		menu.addMenu(eatMenu);
		
		System.out.println(menu.toString());
	}
	
}

//以下部分程式碼使用的不是組合模式實現的==========================================================================
class MilkTea{
	private String name;
	private String desc;
	private int price;
	public MilkTea(String name, String desc, int price) {
		super();
		this.name = name;
		this.desc = desc;
		this.price = price;
	}
	@Override
	public String toString() {
		return "MilkTea [name=" + name + ", desc=" + desc + ", price=" + price
				+ "]";
	}
}

class Juice{
	private String name;
	private String desc;
	private int price;
	public Juice(String name, String desc, int price) {
		super();
		this.name = name;
		this.desc = desc;
		this.price = price;
	}
	@Override
	public String toString() {
		return "Juice [name=" + name + ", desc=" + desc + ", price=" + price
				+ "]";
	}
}

class HandCake{
	private String name;
	private String desc;
	private int price;
	public HandCake(String name, String desc, int price) {
		super();
		this.name = name;
		this.desc = desc;
		this.price = price;
	}
	@Override
	public String toString() {
		return "HandCake [name=" + name + ", desc=" + desc + ", price=" + price
				+ "]";
	}
	
}

class FishBoll{
	private String name;
	private String desc;
	private int price;
	public FishBoll(String name, String desc, int price) {
		super();
		this.name = name;
		this.desc = desc;
		this.price = price;
	}
	@Override
	public String toString() {
		return "FishBoll [name=" + name + ", price=" + price + "]";
	}
	
}

class Menu{
	private String name;
	private String desc;
	private List<Menu> menus = new ArrayList<>();
	private List<MilkTea> milkTeas = new ArrayList<>();
	private List<Juice> juices = new ArrayList<>();
	private List<HandCake> handCakes = new ArrayList<>();
	private List<FishBoll> fishBolls = new ArrayList<>();
	public Menu(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	
	public void addMilkTea(MilkTea milkTea){
		this.milkTeas.add(milkTea);
	}
	public void addJuice(Juice juice){
		this.juices.add(juice);
	}
	public void addHandCake(HandCake handCake){
		this.handCakes.add(handCake);
	}
	public void addFishBoll(FishBoll fishBoll){
		this.fishBolls.add(fishBoll);
	}
	public void addMenu(Menu menu){
		this.menus.add(menu);
	}

	@Override
	public String toString() {
		return "Menu [name=" + name + ", desc=" + desc + ", menus=" + menus
				+ ", milkTeas=" + milkTeas + ", juices=" + juices
				+ ", handCakes=" + handCakes + ", fishBolls=" + fishBolls + "]";
	}
}

列印結果:

Menu [name=大選單, desc=包含所有的子選單, menus=[Menu [name=飲品選單, desc=都是喝的, menus=[], milkTeas=[MilkTea [name=珍珠賣茶, desc=珍珠+奶茶, price=5]], juices=[Juice [name=獼猴桃飲料, desc=無新增劑, price=5]], handCakes=[], fishBolls=[]], Menu [name=小吃選單, desc=都是吃的, menus=[], milkTeas=[], juices=[], handCakes=[HandCake [name=咖哩魚蛋, desc=微辣, price=6]], fishBolls=[FishBoll [name=培根手抓餅, price=6]]]], milkTeas=[], juices=[], handCakes=[], fishBolls=[]]

使用組合模式寫選單

示例程式碼:

abstract class AbstractMenu{
	public abstract void add(AbstractMenu menu);
	public abstract AbstractMenu get(int index);
	public abstract String getString();
}

class MileTeaNew extends AbstractMenu{
	private String name;
	private String desc;
	private int price;
	public MileTeaNew(String name, String desc, int i) {
		super();
		this.name = name;
		this.desc = desc;
		this.price = i;
	}
	@Override
	public void add(AbstractMenu menu) {
		/*未使用*/
	}
	@Override
	public AbstractMenu get(int index) {
		return null;
	}
	@Override
	public String getString() {
		return toString();
	}
	
	@Override
	public String toString() {
		return "MileTeaNew [name=" + name + ", desc=" + desc + ", price="
				+ price + "]";
	}
	
}

class JuiceNew extends AbstractMenu{
	private String name;
	private String desc;
	private int price;
	public JuiceNew(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	@Override
	public void add(AbstractMenu menu) {
		/*未使用*/
	}
	@Override
	public AbstractMenu get(int index) {
		return null;
	}
	@Override
	public String getString() {
		return toString();
	}
	
	@Override
	public String toString() {
		return "MileTeaNew [name=" + name + ", desc=" + desc + ", price="
				+ price + "]";
	}
	
}

class HandCakeNew extends AbstractMenu{
	private String name;
	private String desc;
	private int price;
	public HandCakeNew(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	@Override
	public void add(AbstractMenu menu) {
		/*未使用*/
	}
	@Override
	public AbstractMenu get(int index) {
		return null;
	}
	@Override
	public String getString() {
		return toString();
	}
	
	@Override
	public String toString() {
		return "MileTeaNew [name=" + name + ", desc=" + desc + ", price="
				+ price + "]";
	}
	
}

class FishBollNew extends AbstractMenu{
	private String name;
	private String desc;
	private int price;
	public FishBollNew(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	@Override
	public void add(AbstractMenu menu) {
		/*未使用*/
	}
	@Override
	public AbstractMenu get(int index) {
		return null;
	}
	@Override
	public String getString() {
		return toString();
	}
	
	@Override
	public String toString() {
		return "MileTeaNew [name=" + name + ", desc=" + desc + ", price="
				+ price + "]";
	}
	
}

class MenuNew extends AbstractMenu{
	
	private String name;
	private String desc;
	private List<AbstractMenu> menus = new ArrayList<>();
	public MenuNew(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	@Override
	public void add(AbstractMenu menu) {
		this.menus.add(menu);
	}
	@Override
	public AbstractMenu get(int index) {
		return this.menus.get(index);
	}
	@Override
	public String getString() {
		return toString();
	}
	@Override
	public String toString() {
		return "MenuNew [name=" + name + ", desc=" + desc + ", menus=" + menus
				+ "]";
	}
}

測試例程:

MenuNew menuNew = new MenuNew("大選單","包含所有的子選單");
		MenuNew drinkMenuNew = new MenuNew("飲品選單","都是喝的");
		MenuNew eatMenuNew = new MenuNew("小吃選單","都是吃的");
		
		MileTeaNew milkTeaNew = new MileTeaNew("珍珠賣茶","珍珠+奶茶",6);
		JuiceNew juiceNew = new JuiceNew("獼猴桃飲料","無新增劑");
		HandCakeNew handCakeNew = new HandCakeNew("咖哩魚蛋","微辣");
		FishBollNew fishBollNew = new FishBollNew("培根手抓餅","正宗臺灣風味");
		
		
		drinkMenuNew.add(milkTeaNew);
		drinkMenuNew.add(juiceNew);
		eatMenuNew.add(handCakeNew);
		eatMenuNew.add(fishBollNew);
		menuNew.add(drinkMenuNew);
		menuNew.add(eatMenuNew);
		
		menuNew.getString();

使用了合併模式,如果此時我們要新增一個菜品,只需繼承抽象構建類, 無需改動其他類,顯得更加方便。

概念與總結

三個角色
上面也說了合併模式是用一種樹狀的結構來組合物件,三個名詞 根節點,枝結點,葉子結點,類比上面那個選單的圖, 根節點是選單,枝結點是飲料選單和小吃選單, 葉子結點是奶茶,果汁,手抓餅和魚蛋!

Component:抽象元件,為組合中的物件宣告介面,讓客戶端 可以通過這個介面來訪問和管理整個物件結構,可以在裡面為定義的 功能提供預設的實現,比如上面的AbstractMenu類。
Composite:容器元件,繼承抽象元件,實現抽象元件中與 葉子元件相關的操作,比如上面的Menu類重寫了get,set方法。此處重寫的是toString方法也就是,抽象類中的getString方法
Leaf:葉子元件,定義和實現葉子物件的行為,不再包含其它 的子節點物件,比如上面的MilkTea,Juice,HandCakeFishBall。

UML圖

使用情景

  • 如果你想表示物件的部分-整體層次結構,可以選用組合模式,把整體和部分的操作統一起來,使得層次結構實現更簡單,從外 部來使用這個層次結構也簡單;
  • 如果你希望統一的使用組合結構中的所有物件,可以選用組合模式,這正是組合模式提供的主要功能;

 

優缺點

優點:

讓客戶端更加簡單,客戶端不需要再操心面對的是組合物件還是葉節點物件,所以不需要寫一大堆if語句來保證他們對正確的物件呼叫了正確 的方法。通常,他們只需要對整個結構呼叫一個方法並執行操作就可以了。

缺點:

容易增加新的元件也會帶來一些問題,比如很難限制組合中的元件型別。 這在需要檢測元件型別的時候,使得我們不能依靠編譯期的型別約束來 完成,必須在執行期間動態檢測。