1. 程式人生 > >設計模式(一)(策略模式、觀察者模式)

設計模式(一)(策略模式、觀察者模式)

一:策略模式

策略模式定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

意圖:定義一系列的演算法,把它們一個個封裝起來, 並且使它們可相互替換。

主要解決:在有多種演算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。

何時使用:一個系統有許多許多類,而區分它們的只是他們直接的行為。

如何解決:將這些演算法封裝成一個一個的類,任意地替換。

關鍵程式碼:實現同一個介面。

應用例項: 1、諸葛亮的錦囊妙計,每一個錦囊就是一個策略。 2、旅行的出遊方式,選擇騎自行車、坐汽車,每一種旅行方式都是一個策略。 3、JAVA AWT 中的 LayoutManager。

優點: 1、演算法可以自由切換。 2、避免使用多重條件判斷。 3、擴充套件性良好。

缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。

使用場景: 1、如果在一個系統裡面有許多類,它們之間的區別僅在於它們的行為,那麼使用策略模式可以動態地讓一個物件在許多行為中選擇一種行為。 2、一個系統需要動態地在幾種演算法中選擇一種。 3、如果一個物件有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語句來實現。

注意事項:如果一個系統的策略多於四個,就需要考慮使用混合模式,解決策略類膨脹的問題。

實現:

首先建立一個介面用於運算:

public interface Strategy {
	/**
	 * 運算介面
	 * @param num1
	 * @param num2
	 * @return
	 */
	public int doOperation(int num1, int num2);
}

它有4個實現類分別是加減乘除:

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubstract implements Strategy {
	@Override
	public int doOperation(int num1, int num2) {
		return num1 - num2;
	}
}
public class OperationMultiply implements Strategy {
	@Override
	public int doOperation(int num1, int num2) {
		return num1 * num2;
	}
}
public class OperationDivision implements Strategy {
	@Override
	public int doOperation(int num1, int num2) {
		return num1 / num2;
	}
}

然後我需要一個類來封裝這個介面以及具體實現:

public class Context {
	private Strategy strategy;

	public Context(Strategy strategy) {
		this.strategy = strategy;
	}

	public int executeStrategy(int num1, int num2) {
		return strategy.doOperation(num1, num2);
	}

	public static void main(String[] args) {
		Context context = new Context(new OperationAdd());
		System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

		context = new Context(new OperationSubstract());
		System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

		context = new Context(new OperationMultiply());
		System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
		
		context = new Context(new OperationDivision());
		System.out.println("10 / 5 = " + context.executeStrategy(10, 5));
	}
}

要點:

1、知道OO基礎,並不足以讓你設計出良好的))系統。

2、良好的OO設計必須具備可複用、可擴充、可維護三個特性。

3、模式可以讓我們建造出具有良好OO設計質量的系統。

4、模式被認為是歷經驗證的OO設計經驗。

5、模式不是程式碼,而是針對設計問題的通用解決方案。你可把它們應該到特定的應用中!

6、模式不是被髮明、而是被發現。

7、大多數的模式和原則,都著眼於軟體變化的主題。

8、大多數的模式都允許系統區域性改變獨立於其他部分。

9、我們常把系統中會變化的部分抽出來封裝。

10、模式讓開發人員之間有共享的語言,能夠最大化溝通的價值。
 

二:觀察者模式

觀察者模式定義了物件之間的一對多依賴,這樣一來當一個物件改變狀態時,它的所有依賴者都會收到通知並自動更新。

意圖:定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。

主要解決:一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作。

何時使用:一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。

如何解決:使用面向物件技術,可以將這種依賴關係弱化。

關鍵程式碼:在抽象類裡有一個 ArrayList 存放觀察者們。

應用例項: 1、拍賣的時候,拍賣師觀察最高標價,然後通知給其他競價者競價。 2、西遊記裡面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動作。

優點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。

缺點: 1、如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

使用場景: 1、有多個子類共有的方法,且邏輯相同。 2、重要的、複雜的方法,可以考慮作為模板方法。

注意事項: 1、JAVA 中已經有了對觀察者模式的支援類。 2、避免迴圈引用。 3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式。

實現:

首先建立觀察者介面:

/**
 * 觀察者,所有的觀察者都需要實現update方法
 * @author zzf
 *
 */
public interface Observer {
	public void update(Integer left,Integer right);
}

主題模式介面:

/**
 * 主題介面
 * @author zzf
 *
 */
public interface Subject {

	/**
	 * 用於註冊觀察者
	 * @param o
	 */
	public void registerObserver(Observer o);
	
	/**
	 * 刪除觀察者
	 * @param o
	 */
	public void removeObserver(Observer o);
	
	/**
	 * 當狀態更新時這個方法會被呼叫通知所有觀察者
	 */
	public void notifyObserver();
}

主題實現類,裡面存放了一個維護Observer(觀察者)的列表:

/**
 * 主題的實現類
 * @author zzf
 *
 */
public class OperationData implements Subject{

	private ArrayList<Observer> observersList=new ArrayList<Observer>();
	private Integer left;
	private Integer right;
	
	@Override
	public void registerObserver(Observer o) {
		// TODO Auto-generated method stub
		observersList.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		// TODO Auto-generated method stub
		int i=observersList.indexOf(o);
		if(i>=0) {
			observersList.remove(i);
		}
	}

	@Override
	public void notifyObserver() {
		// TODO Auto-generated method stub
		for(Observer o:observersList)
			o.update(left, right);
	}
	
	/**
	 * 當狀態改變時呼叫notifyObserver,執行觀察者方法
	 */
	public void Changed() {
		notifyObserver();
	}
	
	public void setChanged(Integer left,Integer right) {
		this.left=left;
		this.right=right;
		Changed();
	}

}

下面實現三個Observer的實現類分別實現加減乘:

/* 
 * 觀察者實現類並將自身註冊入Subject
* @author zzf 
* @date 2018年10月17日 上午8:57:05 
*/
public class AddDisplay implements Observer{

	private Integer left;
	private Integer right;
	private Subject subject;
	
	public AddDisplay(Subject subject) {
		this.subject=subject;
		subject.registerObserver(this);//將自身註冊入主題
	}
	
	@Override
	public void update(Integer left, Integer right) {
		// TODO Auto-generated method stub
		this.left=left;
		this.right=right;
		display();
	}
	
	public void display() {
		System.out.println("Add:"+(left+right));
	}

}
/* 
* @author zzf 
* @date 2018年10月17日 上午9:04:19 
*/
public class SubstractDisplay  implements Observer{

	private Integer left;
	private Integer right;
	private Subject subject;
	
	public SubstractDisplay(Subject subject) {
		this.subject=subject;
		subject.registerObserver(this);//將自身註冊入主題
	}
	
	@Override
	public void update(Integer left, Integer right) {
		// TODO Auto-generated method stub
		this.left=left;
		this.right=right;
		display();
	}
	
	public void display() {
		System.out.println("Substract:"+(left-right));
	}

}
/* 
 *觀察者的實現類(乘法)
* @author zzf 
* @date 2018年10月17日 上午9:11:32 
*/
public class MultiplyDisplay implements Observer{

	private Integer left;
	private Integer right;
	private Subject subject;
	
	public MultiplyDisplay(Subject subject) {
		this.subject=subject;
		subject.registerObserver(this);//將自身註冊入主題
	}
	
	@Override
	public void update(Integer left, Integer right) {
		// TODO Auto-generated method stub
		this.left=left;
		this.right=right;
		display();
	}
	
	public void display() {
		System.out.println("Multiply:"+(left*right));
	}

}

下面建立一個測試類,將三個觀察者的實現類註冊入主題模式:

/* 
* @author zzf 
* @date 2018年10月17日 上午9:05:48 
*/
public class Operation {
	
	public static void main(String[] args) {
		//建立主題
		OperationData operationData=new OperationData();
		
		//將觀察者的實現類註冊入主題
		AddDisplay addDisplay=new AddDisplay(operationData);
		SubstractDisplay substractDisplay=new SubstractDisplay(operationData);
		MultiplyDisplay multiplyDisplay=new MultiplyDisplay(operationData);
		
		operationData.setChanged(5,5);
		operationData.setChanged(10, 3);
	}
}

2.通過Java API裡面的java.util包內自帶的Observer介面和Observable類實現觀察者模式(詳細請閱讀原始碼實現原理很簡單)

首先通過繼承java.util.Observable來實現主題的實現類

import java.util.Observable;

/* 
* @author zzf 
* @date 2018年10月17日 下午2:26:34 
*/
public class OperationData extends Observable{

	private Integer left;
	private Integer right;
	
	/**
	 * 當狀態改變時呼叫notifyObserver,執行觀察者方法
	 */
	public void Changed() {
		setChanged();//將changed設定為true
		notifyObservers();
	}
	
	public void setChanged(Integer left,Integer right) {
		this.left=left;
		this.right=right;
		Changed();
	}

	public Integer getLeft() {
		return left;
	}

	public void setLeft(Integer left) {
		this.left = left;
	}

	public Integer getRight() {
		return right;
	}

	public void setRight(Integer right) {
		this.right = right;
	}
	
}

然後通過實現java.util.Observer來實現觀察類:

public class MultiplyDisplay implements Observer{

	private Integer left;
	private Integer right;
	private Observable observable;
	
	public MultiplyDisplay(Observable observable) {
		this.observable=observable;
		observable.addObserver(this);//將自身註冊入主題
	}
	
	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		if(o instanceof OperationData) {
			OperationData operationData = (OperationData)o;
			this.left=operationData.getLeft();
			this.right=operationData.getRight();
			display();
		}
	}
	
	public void display() {
		System.out.println("Multiply:"+(left*right));
	}
}
import java.util.Observable;
import java.util.Observer;

/* 
* @author zzf 
* @date 2018年10月17日 下午2:31:37 
*/
public class AddDisplay implements Observer{

	private Integer left;
	private Integer right;
	private Observable observable;
	
	public AddDisplay(Observable observable) {
		this.observable=observable;
		observable.addObserver(this);//將自身註冊入主題
	}
	
	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		if(o instanceof OperationData) {
			OperationData operationData = (OperationData)o;
			this.left=operationData.getLeft();
			this.right=operationData.getRight();
			display();
		}
	}
	
	public void display() {
		System.out.println("Add:"+(left+right));
	}

}

執行後:

要點:

1、觀察者模式定義了物件之間一對多的關係。

2、主題(可觀察者)用一個共同的介面來更新觀察者。

3、觀察者和可觀察者之間用鬆耦合方式結合,可觀察者不知道觀察者的細節,只知道觀察者實現了觀察者介面。

4、使用哦此模式時,你可從被觀察者處推或拉資料。

5、有多個觀察者模式時,不可以依賴特定的通知次序。

6、要注意java.util.Observable實現上帶來的一些問題(是一個類非介面,違反了針對介面程式設計)。

7、如果有必要,可以實現自己的Observable。

8、Swing中運用了大量觀察者模式,許多GUI框架也是如此。

參考:https://www.w3cschool.cn/shejimoshi/strategy-pattern.html

           https://www.w3cschool.cn/shejimoshi/observer-pattern.html