1. 程式人生 > >觀察者模式+中介者模式。

觀察者模式+中介者模式。

有一個產品,他有多個觸發事件,他產生的時候觸發一個建立事件,修改的時候觸發修改事件,刪除的時候觸發刪除事件,這就類似於我們的文字框,初始化(也就是建立)的時候要觸發一個onLoad或onCreate事件,修改的時候觸發onChange事件,雙擊(類似於刪除)的時候又觸發onDbClick事件,我們今天的目標就是來思考怎麼實現這樣一個架構。

設計都是先易後難,我們先從最簡單的部分入手。首先需要一個產品,並且該產品要有建立、修改、銷燬的動作,很明顯這就是一個工廠方法模式。同時產品也可以通過克隆方式產生,這與我們在GUI設計中經常使用的複製貼上操作相類似,這非常明顯就是原型模式。

我們使用了工廠方法模式建立產品,使用原型模式讓物件可以被拷貝,僅僅這兩個模式還不足以解決我們的問題,想想看,產品的產生是有一定的條件的,不是誰想產生就產生,否則怎麼能夠觸發建立事件呢?因此需要限定產品的建立者,所以我們把產品和工廠的關係定位為組合關係,而不是簡單地聚集或依賴關係。換句話說,產品只能由工廠類建立,而不能被其他物件通過new方式建立,因此我們在這裡還用到一個單來源呼叫(Single Call)方法來解決問題。這是一個方法,不是一個設計模式。

我們先來看產品類的原始碼,他比較簡單,如下所示。

public class Product implements Cloneable {
	// 產品名稱
	private String name;
	// 是否可以屬性變更
	private boolean canChanged = false;

	/**
	 * 產生一個新的產品
	 * 
	 * @param manager
	 * @param _name
	 */
	public Product(ProductManager manager, String _name) {
		// 允許建立產品
		if (manager.isCreateProduct()) {
			this.canChanged = true;
			this.name = _name;
		}
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		if (this.canChanged) {
			this.name = name;
		}
	}

	@Override
	protected Product clone() {
		Product p = null;
		try {
			p = (Product) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}

在產品類中,我們只定義產品的一個屬性:產品名稱(name),並實現了getter/setter方法,然後我們實現了他的clone方法,確保物件是可以被拷貝的。還有一個特殊的地方是我們的建構函式,他怎麼會要求傳遞進來一個工廠物件ProductManager呢?保留你的好奇心,馬上為你揭曉答案。我們繼續看程式碼,工廠類如下所示。

public class ProductManager {
	// 是否可以建立一個產品
	private boolean isPermittedCreate = false;

	/**
	 * 建立一個產品
	 * 
	 * @param name
	 * @return
	 */
	public Product createProduct(String name) {
		// 首先修改許可權,允許建立
		this.isPermittedCreate = true;
		Product p = new Product(this, name);
		return p;
	}

	/**
	 * 廢棄一個產品
	 * 
	 * @param p
	 */
	public void abandonProduct(Product p) {
		// 銷燬一個產品,例如刪除資料庫記錄
		p = null;
	}

	/**
	 * 修改一個產品
	 * 
	 * @param p
	 * @param name
	 */
	public void editProduct(Product p, String name) {
		// 修改後的產品
		p.setName(name);
	}

	/**
	 * 獲得是否可以建立一個產品
	 * 
	 * @return
	 */
	public boolean isCreateProduct() {
		return this.isPermittedCreate;
	}

	/**
	 * 克隆一個產品
	 * 
	 * @param p
	 * @return
	 */
	public Product clone(Product p) {
		return p.clone();
	}
}

仔細看看工廠類,產品的建立、修改、遺棄、克隆方法都很簡單,但有一個方法可不簡單——isCreateProduct方法,他的作用是告訴產品類“我是能建立產品的”,注意看我們的程式,在工廠類ProductManager中定義了一個私有變數isCreateProduct,該變數只有在工廠類的createProduct函式中才能設定為true,在建立產品的時候,產品類Product的建構函式要求傳遞工廠物件,然後判斷是否能夠建立產品,即使你想使用類似這樣的方法:

Product p = new Product(new ProductManager(), "abc");

也是不可能創建出產品的,他在產品中限制必須是當前有效工廠才能生產該產品,而且也只有有效地工廠才能修改產品,看看產品類的canChanged屬性,只有他為true時,產品才可以修改,產品就問“你有權力建立我嗎”於是工廠類出示了兩個證明材料證明自己可以建立產品:一是“我是你的工廠類”,二是“我的isCreateProduct返回true,我有權建立”,於是產品就被創建出來了。這種一個物件只能由固定的物件初始化的方法叫做單來源呼叫(Single Call)——很簡單,但非常有用的方法。

注意:採用單來源呼叫的兩個物件一般是組合關係,兩者有相同的生命期,他通常適用於有單例模式和工廠方法模式的場景中。

我們繼續往下分析,一個產品新建就要觸發事件,那事件是什麼?當然也是一個物件了,需要把他設計出來,僅僅有事件還不行,還要考慮有人去處理這個事件,產生了一個事件不可能沒有物件去處理吧?如果是這樣那事件還有什麼意義呢?既然去處理,那就需要一個通知渠道了,於是觀察者模式準備好了。

觀察者為EventDispatch類,他使用了單例模式,避免物件膨脹,但同時也帶來了效能及執行緒安全隱患,這點需要大家在實際應用中注意(想想Spring中的Bean注入,預設也是單例,在通常的應用中一般不需要修改,除非是較大併發的應用)。我們來看程式碼,先來看事件型別定義,他是一個列舉型別,如下所示。

public enum ProductEventType {
	// 新建一個產品
	NEW_PRODUCT(1),
	// 刪除一個產品
	DEL_PRODUCT(2),
	// 修改一個產品
	EDIT_PRODUCT(3),
	// 克隆一個產品
	CLONE_PRODUCT(4);

	private int vlaue = 0;

	private ProductEventType(int vlaue) {
		this.vlaue = vlaue;
	}

	public int getVlaue() {
		return vlaue;
	}
}

這裡定義了4個事件型別,分別是新建、修改、刪除以及克隆,比較簡單。我們再來看產品的事件,如下所示。

public class ProductEvent extends Observable {
	// 事件起源
	private Product source;
	// 事件的型別
	private ProductEventType type;

	/**
	 * 傳入事件的源頭,預設為新建型別
	 * 
	 * @param p
	 */
	public ProductEvent(Product p) {
		this(p, ProductEventType.NEW_PRODUCT);
	}

	/**
	 * 事件源頭以及事件型別
	 * 
	 * @param p
	 * @param type
	 */
	public ProductEvent(Product p, ProductEventType type) {
		this.source = p;
		this.type = type;
		// 事件觸發
		this.notifyEventDispatch();
	}

	/**
	 * 獲得事件的始作俑者
	 * 
	 * @return
	 */
	public Product getSource() {
		return source;
	}

	/**
	 * 獲得事件的型別
	 * 
	 * @return
	 */
	public ProductEventType getEventType() {
		return this.type;
	}

	/**
	 * 通知事件處理中心
	 */
	private void notifyEventDispatch() {
		super.addObserver(EventDispatch.getEventDispatch());
		super.setChanged();
		super.notifyObservers(source);
	}
}

我們在產品事件類中增加了一個私有方法notifyEventDispatch,該方法的作用是明確事件的觀察者,並同時在初始化時通知觀察者,他在有參構造中被呼叫。我們再來看事件的觀察者,如下所示。

public class EventDispatch implements Observer {
	// 單例模式
	private final static EventDispatch dispatch = new EventDispatch();
	// 事件消費者
	private Vector<EventCustomer> customer = new Vector<EventCustomer>();

	/**
	 * 不允許生成新的例項
	 */
	private EventDispatch() {
	}

	/**
	 * 獲得單例物件
	 * 
	 * @return
	 */
	public static EventDispatch getEventDispatch() {
		return dispatch;
	}

	@Override
	public void update(Observable o, Object arg) {
		
	}

}

產品和事件都定義出來了,那我們想想怎麼把這兩者關聯起來,產品和事件是兩個獨立的物件,兩者都可以獨立的擴充套件,用什麼來適應他們的擴充套件呢?橋樑模式!兩個不相關的類可以通過橋樑模式組合出穩定、健壯的結構。

我們這次把抽象化角色和實現角色去掉,各位可能要說了,把抽象化角色和實現化角色去掉,那橋樑模式在抽象層次耦合的優點還怎麼體現呢?因為我們採用的是單個產品物件,沒有必要進行抽象化處理,若要按照該框架做擴充套件開發,該部分是肯定需要抽象出介面或抽象類的,好在也非常簡單,只要抽取一下就可以了。這樣考慮後,我們的ProductManager類就增加一個功能:組合產品類和事件類,產生有意義的產品事件,如下所示。

public class ProductManager {
	// 是否可以建立一個產品
	private boolean isPermittedCreate = false;

	/**
	 * 建立一個產品
	 * 
	 * @param name
	 * @return
	 */
	public Product createProduct(String name) {
		// 首先修改許可權,允許建立
		this.isPermittedCreate = true;
		Product p = new Product(this, name);
		// 產生一個建立事件
		new ProductEvent(p, ProductEventType.NEW_PRODUCT);
		return p;
	}

	/**
	 * 廢棄一個產品
	 * 
	 * @param p
	 */
	public void abandonProduct(Product p) {
		// 銷燬一個產品,例如刪除資料庫記錄
		// 產生刪除事件
		new ProductEvent(p, ProductEventType.DEL_PRODUCT);
		p = null;
	}

	/**
	 * 修改一個產品
	 * 
	 * @param p
	 * @param name
	 */
	public void editProduct(Product p, String name) {
		// 修改後的產品
		p.setName(name);
		// 產生修改事件
		new ProductEvent(p, ProductEventType.EDIT_PRODUCT);
	}

	/**
	 * 獲得是否可以建立一個產品
	 * 
	 * @return
	 */
	public boolean isCreateProduct() {
		return this.isPermittedCreate;
	}

	/**
	 * 克隆一個產品
	 * 
	 * @param p
	 * @return
	 */
	public Product clone(Product p) {
		// 產生克隆事件
		new ProductEvent(p, ProductEventType.CLONE_PRODUCT);
		return p.clone();
	}
}

在每個方法中增加了事件的產生機制,在createProduct方法中增加了建立產品事件,在editProduct方法中增加了修改產品事件,在delProduct方法中增加了遺棄產品事件,在clone方法中增加克隆產品事件,而且每個事件都是通過組合產生的,產品和事件的擴充套件性非常優秀。

剛剛我們說完了產品和事件的關係處理,現在回到我們事件的觀察者,他承擔著非常重要的職責。我們知道他要處理事件,但是現在還沒有想好怎麼實現他處理事件的update方法,暫時保持為空。

我們繼續分析,這麼多事件(現在只有1個產品類,如果產品類很多呢?)不可能每個產品事件都寫一個處理者吧,對於產品事件來說,他最希望的結果就是我通知了事件處理者(也就是觀察者模式的觀察者),其他具體怎麼處理由觀察者來解決,那現在問題是觀察者怎麼來處理這麼多的事件呢?事件的處理者必然有N多個,如何才能通知相應的處理者來處理事件呢?一個事件也可能通知多個處理者來處理,並且一個處理者處理完畢還可能通知其他的處理者,還不可能讓每個處理者獨自完成這樣“不可能完成的任務”。

是的,需要中介者模式上場了,我們把EventDispatch類作為事件分發的中介者,事件的處理者都是具體的同事類,他們有著相似的行為,都是處理產品事件,但是又有不相同的邏輯,每個同事類對事件都有不同的處理行為。

EventDispatch類有3個職責。

  • 事件的觀察者

作為觀察者模式中的觀察者角色,接收被觀察者期望完成的任務,在我們的框架中就是接收ProductEvent事件。

  • 事件分發者

作為中介者模式的中介者角色,他擔當著非常重要的任務——分發事件,並同時協調各個同事類(也就是事件的處理者)處理事件。

  • 事件處理者的管理員角色

不是每一個事件的處理者都可以接收事件並進行處理,是需要獲得分發者許可後才可以,也就是說只有事件分發者允許他處理,他才能處理。

事件分發者擔當了這麼多的職責,那是不是與單一職責原則相違背了?確實如此,我們在整個系統的設計中確實需要這樣一個角色擔任這麼多的功能,如果強制細分也可以完成,但是會加大程式碼量,同時導致系統的結構複雜,可以考慮拆分這3個職責,然後再組合相關的功能,看看程式碼量是如何翻倍的。

注意,設計原則只是一個理論,而不是一個帶有刻度的標尺,因此在系統設計中不應該把他視為不可逾越的屏障,而是應該把他看成是一個方向標,儘量遵守,而不是必須恪守。

盡然事件分發者這麼重要,我們就仔細研讀一下他的程式碼,如下所示。

public class EventDispatch implements Observer {
	// 單例模式
	private final static EventDispatch dispatch = new EventDispatch();
	// 事件消費者
	private Vector<EventCustomer> customer = new Vector<EventCustomer>();

	/**
	 * 不允許生成新的例項
	 */
	private EventDispatch() {
	}

	/**
	 * 獲得單例物件
	 * 
	 * @return
	 */
	public static EventDispatch getEventDispatch() {
		return dispatch;
	}

	@Override
	public void update(Observable o, Object arg) {
		// 事件
		ProductEvent event = (ProductEvent) o;
		// 處理者處理,這裡是中介者模式的核心,可以是很複雜的業務邏輯
		for (EventCustomer e : this.customer) {
			// 處理能力是否匹配
			for (EventCustomType t : e.getCustomType()) {
				if (t.getValue() == event.getEventType().getVlaue()) {
					e.exec(event);
				}
			}
		}
	}

	/**
	 * 註冊事件處理者
	 * 
	 * @param _customer
	 */
	public void registerCustomer(EventCustomer _customer) {
		this.customer.add(_customer);
	}

}

我們在這裡使用Vector來儲存所有的事件處理者,在update方法中使用了兩個簡單地for迴圈來完成業務邏輯的判斷,只要事件的處理者級別和事件的型別相匹配,就呼叫事件處理者的exec方法來處理事件,該邏輯是整個事件觸發架構的關鍵點,但不是難點。請注意,在設計這樣的框架前,一定要定義好消費者與生產者之間的搭配問題,一般的做法是通過xml檔案類或者IoC容器配置規則,然後在框架啟動時載入並駐留記憶體。

EventCustomer抽象類負責定義事件處理者必須具有的行為,首先是每一個事件的處理者都必須定義自己能夠處理的級別,也就是通過建構函式來定義自己的處理能力,當然處理能力可以是多值的,也就是說一個處理者可以處理多個事件;然後各個事件的處理者只要實現exec方法就可以了,完成自己對事件的消費處理即可。我們先來看抽象的事件處理者,如下所示。

public abstract class EventCustomer {
	// 容納每個消費者能夠處理的級別
	private Vector<EventCustomType> customType = new Vector<EventCustomType>();

	/**
	 * 每個消費者都要宣告自己處理哪一類別的事件
	 * 
	 * @param customType
	 */
	public EventCustomer(EventCustomType customType) {
		addCustomType(customType);
	}

	/**
	 * 每個消費者可以消費多個事件
	 * 
	 * @param type
	 */
	public void addCustomType(EventCustomType type) {
		this.customType.add(type);
	}

	/**
	 * 得到自己的能力
	 * 
	 * @return
	 */
	public Vector<EventCustomType> getCustomType() {
		return customType;
	}

	/**
	 * 每個事件都要對事件進行宣告式消費
	 * 
	 * @param event
	 */
	public abstract void exec(ProductEvent event);
}

很簡單,我們定義了一個Vector變數來儲存處理者的處理能力,然後通過建構函式約束子類必須定義一個自己的處理能力。在程式碼中,我們用到了事件處理型別列舉,如下所示。

public enum EventCustomType {
	// 新建立事件
	NEW(1),
	// 刪除事件
	DEL(2),
	// 修改事件
	EDIT(3),
	// 克隆事件
	CLONE(4);

	private int value = 0;

	private EventCustomType(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}
}

我們在系統中定義了3個事件處理者,分別是乞丐、平民和貴族。乞丐只能獲得別人遺棄的物品,平民消費自己生產的東西,自給自足,而貴族則可以獲得精修的產品或者是綠色產品(也就是我們這裡的克隆產品,不用自己勞動獲得的產品)。我們先看乞丐的原始碼,如下所示。

public class Beggar extends EventCustomer {
	/**
	 * 只能處理被人遺棄的東西
	 */
	public Beggar() {
		super(EventCustomType.DEL);
	}

	@Override
	public void exec(ProductEvent event) {
		// 事件的源頭
		Product p = event.getSource();
		// 事件型別
		ProductEventType type = event.getEventType();
		System.out.println("乞丐處理事件:" + p.getName() + "銷燬,事件型別=" + type);
	}

}

乞丐在無參構造中定義了自己只能處理刪除的事件,然後在exec方法中定義了事件的處理邏輯,每個處理者都是隻要完成這兩個方法即可,我們再來看平民級別的事件處理者,如下所示。

public class Commoner extends EventCustomer {
	/**
	 * 定義平民能夠處理的事件的級別
	 */
	public Commoner() {
		super(EventCustomType.NEW);
	}

	@Override
	public void exec(ProductEvent event) {
		// 事件的源頭
		Product p = event.getSource();
		// 事件型別
		ProductEventType type = event.getEventType();
		System.out.println("平民處理事件:" + p.getName() + "誕生記,事件型別=" + type);
	}

}

平民只處理新建立的事件,其他事件不做處理,我們再來看貴族級別的事件處理者,如下所示。

public class Nobleman extends EventCustomer {
	/**
	 * 定義貴族能夠處理的事件的級別
	 */
	public Nobleman() {
		super(EventCustomType.EDIT);
		super.addCustomType(EventCustomType.CLONE);
	}

	@Override
	public void exec(ProductEvent event) {
		// 事件的源頭
		Product p = event.getSource();
		// 事件型別
		ProductEventType type = event.getEventType();
		if (type.getVlaue() == EventCustomType.CLONE.getValue()) {
			System.out.println("貴族處理事件:" + p.getName() + "克隆,事件型別=" + type);
		} else {
			System.out.println("貴族處理事件:" + p.getName() + "修改,事件型別=" + type);
		}
	}

}

貴族稍有不同,他有兩個處理能力,能夠處理修改事件和克隆事件,同時在exec方法中對這兩類事件分別進行處理。此時,可能會想到另外一個處理模式:責任鏈模式。建立一個鏈,然後兩類事件分別在鏈上進行處理並反饋結果。可以參考一下Servlet的過濾器的設計,在框架平臺的開發中可以採用該模式,他具有非常好的擴充套件性和穩定性。

所有的角色都已出場,我們建立一個場景類把他們串聯起來,如下所示。

public class Client {
	public static void main(String[] args) {
		// 獲得事件分發中心
		EventDispatch dispatch = EventDispatch.getEventDispatch();
		// 接受乞丐對事件的處理
		dispatch.registerCustomer(new Beggar());
		// 接受平民對事件的處理
		dispatch.registerCustomer(new Commoner());
		// 接受貴族對事件的處理
		dispatch.registerCustomer(new Nobleman());
		// 建立一個原子彈生產工廠
		ProductManager factory = new ProductManager();
		// 製造一個產品
		System.out.println("=====模擬建立產品事件=====");
		System.out.println("建立一個叫做小男孩的原子彈");
		Product p = factory.createProduct("小男孩原子彈");
		// 修改一個產品
		System.out.println("\n=====模擬修改產品事件=====");
		System.out.println("把小男孩原子彈修改為胖子號原子彈");
		factory.editProduct(p, "胖子號原子彈");
		// 再克隆一個原子彈
		System.out.println("\n=====模擬克隆產品事件=====");
		System.out.println("克隆胖子號原子彈");
		factory.clone(p);
		// 遺棄一個產品
		System.out.println("\n=====模擬銷燬產品事件=====");
		System.out.println("遺棄胖子號原子彈");
		factory.abandonProduct(p);
	}
}

我們的事件處理框架已經生效了,有行為,就產生事件,並有處理事件的處理者,並且這三者都相互解耦,可以獨立的擴充套件下去。比如,想增加處理者,沒有問題,建立一個類繼承EventCustomer,然後註冊到EventDispatch上,就可以進行處理事件了;想擴充套件產品,沒問題?需要稍稍修改一下,首先抽取出產品和事件的抽象類,然後再進行擴充套件即可。

小結

該事件觸發框架結構清晰,擴充套件性好,可以進行抽象化處理後應用於實際開發中。我們回頭看看在這個案例中使用了哪些設計模式。

  • 工廠方法模式

負責產生產品物件,方便產品的修改和擴充套件,並且實現了產品和工廠的緊耦合,避免產品隨意被建立而無觸發事件的情況發生。

  • 橋樑模式

在產品和事件兩個物件的關係中我們使用了橋樑模式,如此設計後,兩者都可以自由的擴充套件(前提是需要抽取抽象化)而不會破壞原有的封裝。

  • 觀察者模式

觀察者模式解決了事件如何通知處理者的問題,而且觀察者模式還有一個優點是可以有多個觀察者,也就是我們的架構是可以多層級、多分類的處理者。想重新擴充套件一個新型別(新介面)的觀察者?沒有問題,擴充套件ProductEvent即可。

  • 中介者模式

事件有了,處理者也有了,這些都會發生變化,並且處理者之間也有耦合關係,中介者則可以完美的處理這些複雜的關係。

我們再來思考一下,如果我們要擴充套件這個框架,可能還會用到什麼模式?首先是責任鏈模式,他可以幫助我們解決一個處理者處理多個事件的問題;其次是模板方法模式,處理者的啟用、停用等,都可以通過模板方法模式來好似西安;再次是裝飾模式,事件的包裝、處理者功能的強化都會用到裝飾模式。當然了,我們還可能用到其他的模式,只要能夠很好的解決我們的困境,那就好好使用吧,這也是我們學習設計模式的目的。