策略模式- Strategy

1、編寫鴨子項目,具體要求如下:

  1. 有各種鴨子(比如 野鴨、北京鴨、水鴨等, 鴨子有各種行爲,比如 叫、飛行等)

  2. 顯示鴨子的信息

2 傳統方案解決鴨子問題的分析和代碼實現

  1. 傳統的設計方案(類圖)

在這裏插入圖片描述

2) 代碼實現

抽象類 Duck

public abstract class Duck {
	
	/**
	 * 顯示鴨子信息
	 */
	public abstract void display();

	public void quack() {
		System.out.println("鴨子嘎嘎叫~~~~~");
	}
	
	public void swim() {
		System.out.println("鴨子會游泳~~~~");
	}
	
	public void fly() {
		System.out.println("鴨子會飛翔~~~~");
	}
}




** 北京鴨 **

public class PekingDuck  extends Duck{

	@Override
	public void display() {
		System.out.println("~北京鴨~");
	}
	
	/**
	 * 因爲北京鴨不能飛翔,因此需要重寫fly
	 */
	public void fly() {
		System.out.println("北京鴨不能飛翔~~~");
	}

}

** 野鴨 **

public class WildDuck extends Duck {

	@Override
	public void display() {
		System.out.println(" 這是野鴨 ");
	}

}

** 玩具鴨 **


public class ToyDuck extends Duck{

	@Override
	public void display() {
		System.out.println("玩具鴨");
	}
	//需要重寫父類的所有方法
	
	public void quack() {
		System.out.println("玩具鴨不能叫~~");
	}
	
	public void swim() {
		System.out.println("玩具鴨不會游泳~~");
	}
	
	public void fly() {
		System.out.println("玩具鴨不會飛翔~~~");
	}
}


3.傳統的方式實現的問題分析和解決方案

  1. 其它鴨子,都繼承了 Duck 類,所以 fly 讓所有子類都會飛了,這是不正確的

  2. 上面說的 1 的問題,其實是繼承帶來的問題:對類的局部改動,尤其超類的局部改動,會影響其他部分。會有溢出效應

  3. 爲了改進 1 問題,我們可以通過覆蓋 fly 方法來解決 => 覆蓋解決

  4. 問題又來了,如果我們有一個玩具鴨子 ToyDuck, 這樣就需要 ToyDuck 去覆蓋 Duck 法 的所有實現的方法 => 解決思路 -》 式 策略模式 (strategy pattern)

4. 策略模式基本介紹

  1. 策略模式(Strategy Pattern)中,定義 算法族(策略組),分別封裝起來,讓他們之間可以互相替換,此模式讓 算法的變化獨立於 使用算法的客戶

  2. 這算法體現了幾個設計原則,第一、把變化的代碼從不變的代碼中分離出來;第二、針對接口編程而不是具體類(定義了策略接口);第三、多用組合/聚合,少用繼承(客戶通過組合方式使用策略)

5. 策略模式的原理類圖

在這裏插入圖片描述
說明:從上圖可以看到,客戶 context 有成員變量 strategy 或者其他的策略接口
,至於需要使用到哪個策略,我們可以在構造器中指定

6. 策略模式解決鴨子問題

  1. 應用實例要求
    編寫程序完成前面的鴨子項目,要求使用策略模式
  2. 思路分析(類圖)
    策略模式:分別封裝行爲接口,實現算法族,超類裏放行爲接口對象,在子類裏具體設定行爲對象。原則就是:
    分離變化部分,封裝接口,基於接口編程各種功能。此模式讓行爲的變化獨立於算法的使用者

在這裏插入圖片描述


public interface FlyBehavior {
	void fly(); // 子類具體實現
}

public class GoodFlyBehavior implements FlyBehavior{
	@Override
	public void fly() {
		System.out.println(" 飛翔技術高超 ~~~");
	}
}


public class BadFlyBehavior implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println(" 飛翔技術一般 ");
	}
}




public class NoFlyBehavior  implements FlyBehavior{
	@Override
	public void fly() {
		System.out.println(" 不會飛翔  ");
	}
}


public interface SwimBehavior {
	void swim();//子類實現
}

public class GoodSwimBehavior implements SwimBehavior{
	@Override
	public void swim() {
		System.out.println(" 游泳技術高超 ~~~");
	}
}

public class BadSwimBehavior implements SwimBehavior {
	@Override
	public void swim() {
		System.out.println(" 游泳技術一般 ");
	}
}


public class NoSwimBehavior  implements SwimBehavior{
	@Override
	public void swim() {
		System.out.println(" 不會游泳~~~~~  ");
	}
}


public abstract class Duck {

	protected FlyBehavior flyBehavior;
	
	protected SwimBehavior swimBehavior;
	
	public abstract void display();//顯示鴨子信息
	
	public void fly() {
		if(this.flyBehavior!=null) {
			fly();
		}
	}
	
	public void swim() {
		if(this.swimBehavior!=null) {
			swim();
		}
	}

	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public void setSwimBehavior(SwimBehavior swimBehavior) {
		this.swimBehavior = swimBehavior;
	}
	
	
}


public class ToyDuck extends Duck{

	@Override
	public void display() {
		System.out.println("玩具鴨~~~~~");
	}
	
	public ToyDuck() {
		flyBehavior = new NoFlyBehavior();
		swimBehavior = new NoSwimBehavior();
	}
	

}



public class WildDuck extends Duck{

	@Override
	public void display() {
		System.out.println("~~ 野鴨 ~~~");
	}
	
	public WildDuck() {
		flyBehavior = new GoodFlyBehavior();
		swimBehavior = new GoodSwimBehavior();
	}

}
public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WildDuck wildDuck = new WildDuck();
		wildDuck.fly();//
		
		ToyDuck toyDuck = new ToyDuck();
		toyDuck.fly();
		
		PekingDuck pekingDuck = new PekingDuck();
		pekingDuck.fly();
		
		//動態改變某個對象的行爲, 北京鴨 不能飛
		pekingDuck.setFlyBehavior(new NoFlyBehavior());
		System.out.println("北京鴨的實際飛翔能力");
		pekingDuck.fly();
	}

}

7.策略模式在 JDK-Arrays 應用的源碼分析

  1. JDK 的 Arrays 的 Comparator 就使用了策略模式

  2. 代碼分析+Debug 源碼+模式角色分析

在這裏插入圖片描述

8.支付的例子

需求: 工資支付方式的問題。很多其他的 工資方式方式是很靈活的,可支付方式是比較多的,比如,人民幣現金支付、美元現金支付、銀行轉賬到工資賬戶、銀行轉賬到工資開;總之,工資支付方式很多。

要實現這樣的功能,策略模式是一個很好的選擇。在實現這個功能的時候,不同的策略算法需要的數據是不一樣的,比如現金支付就不需要銀行賬戶,而銀行轉賬就需要賬號。這導致在設計策略接口中的方法時,不太好確定參數的個數,而且,就算現在把所有的參數都列上了,今後擴展呢?難道再來修改策略接口嗎?如果這樣做,那無異於異常災難,加入一個新策略,就需要修改接口,然後修改所有已有的實現。那麼到底如何實現,在今後口占的時候才方便呢?

實現代碼示例:

1、先定義工資支付的 策略接口,也就是定義一個支付工資的方法。代碼如下:

/**
 * 支付工資的策略接口,公司有多種支付工資的算法
 * 比如,現金、銀行卡、現金加股票、現金加期權、美元支付等
 * @author Administrator
 *
 */
public interface PaymentStrategy {
	/**
	 * 公司給某人真正支付工資
	 * @param ctx 支付工資 的上下文,裏面包含算法需要的數據
	 */
	public void pay(PaymentContext ctx);

}

2、定義好工資支付的策略接口,該來考慮如何實現這多種支付策略了。

人民幣現金支付的策略實現。示例 代碼如下:


/**
 * 人民幣現金支付
 * @author Administrator
 *
 */
public class RMBCash implements PaymentStrategy{

	@Override
	public void pay(PaymentContext ctx) {
		System.out.println("現在給"+ctx.getUserName()+" 人民幣現金支付" + ctx.getMoney()+" 元");
	}
}

同樣地實現美元現金支付的策略,示例代碼如下:

/**
 * 美元支付
 * @author Administrator
 *
 */
public class DollerCashStrategy implements PaymentStrategy{
	@Override
	public void pay(PaymentContext ctx) {
		System.out.println("現在給"+ctx.getUserName()+" 美元現金支付" + ctx.getMoney()+" 元");
	}
}
  1. 支付上下文的實現,是需要知道具體使用哪一個支付策略的,一般由客戶端來確定具體使用哪一種具體的策略,然後上下文去 真正執行。因此,這個上下文需要持有一個支付策略,而且是由客戶端來配置它。示例代碼如下:
/**
 * 支付工資的上下文,每個人的工資不同,支付方式也不同
 * @author Administrator
 *
 */
public class PaymentContext {
	
	/**
	 * 應被支付工資的人員
	 */
	private String userName = null;
	
	/**
	 * 應被支付的工資金額
	 */
	private double money = 0.0;
	
	private PaymentStrategy strategy = null;

	public String getUserName() {
		return userName;
	}

	public double getMoney() {
		return money;
	}

	public PaymentStrategy getStrategy() {
		return strategy;
	}

	public PaymentContext(String userName, double money, PaymentStrategy strategy) {
		this.userName = userName;
		this.money = money;
		this.strategy = strategy;
	}
	/**
	 * 立即支付工資
	 */
	public void payNow() {
		this.strategy.pay(this);
	}
	

}

4) 客戶端調用

public class Client {
	
	public static void main(String[] args) {
		//創建相應的支付策略
		PaymentStrategy strategyRMB = new RMBCash();
		PaymentStrategy strategyDollar = new DollerCashStrategy();
		PaymentStrategy strategyCard = new CardStrategy();
		
		
		//準備小李 的支付工資上下文
		PaymentContext ctx1 = new PaymentContext("小李", 5000 , strategyRMB);
		
		//向小李支付工資
		ctx1.payNow();
		
		
		PaymentContext ctx2 = new PaymentContext("小張", 175000 , strategyDollar);
		ctx2.payNow();
		

		PaymentContext ctx3 = new PaymentContext2("小王", 275000 ,"342222115555545554", strategyCard);
		ctx3.payNow();
		
	}

}

** 擴展示例,實現方式一 **

如果現在 要增加一種支付方式,要求能支付到銀行卡,該怎樣擴展最簡單呢?

應該新增一種支付到銀行卡的策略實現,然後通過繼承來擴展支付上下文,在其中添加新的支付需要的新 數據,比如銀行卡賬戶,並在客戶端使用新的上下文和新的策略實現就可以了,這樣已有的實現都不需要改變,完全遵循開閉原則


/**
 * 擴展支付上下文對象
 * @author Administrator
 *
 */
public class PaymentContext2 extends PaymentContext{
	
	/**
	 * 銀行賬戶
	 */
	private String account = null;

	public PaymentContext2(String userName, double money,String  account, PaymentStrategy strategy) {
		super(userName, money, strategy);
		this.account = account;
	}

	public String getAccount() {
		return account;
	}
	

}

然後看看新的策略算法的實現。示例代碼如下:

public class CardStrategy implements PaymentStrategy{

	//這個新的算法自己知道使用擴展的支付上下文,所以強制造型以下
	@Override
	public void pay(PaymentContext ctx) {
		PaymentContext2 ctx2 = (PaymentContext2)ctx;
		System.out.println("現在給 " + ctx2.getUserName() +"的 "+ctx2.getAccount() +" 賬號支付了 "+ ctx2.getMoney()+"元");
		//連接銀行,進行轉賬
		
	}

}

客戶端代碼實現:


public class Client {
	
	public static void main(String[] args) {
		//創建相應的支付策略
		PaymentStrategy strategyRMB = new RMBCash();
		PaymentStrategy strategyDollar = new DollerCashStrategy();
		PaymentStrategy strategyCard = new CardStrategy();
		
		
		//準備小李 的支付工資上下文
		PaymentContext ctx1 = new PaymentContext("小李", 5000 , strategyRMB);
		
		//向小李支付工資
		ctx1.payNow();
		
		PaymentContext ctx2 = new PaymentContext("小張", 175000 , strategyDollar);
		ctx2.payNow();
		
		PaymentContext ctx3 = new PaymentContext2("小王", 275000 ,"342222115555545554", strategyCard);
		ctx3.payNow();
		
	}
}

擴展示例,實現方式二

上面的實現,是通過擴展上下文對象來準備新的算法需要的數據。還有另外一種方式,那就是通過策略的構造方法來傳入新算法需要的數據。這種實現的話,就不需要擴展上下文了,直接添加新的策略算法實現就可以了。實例代碼如下:

/**
 * 支付到銀行卡
 * @author Administrator
 *
 */
public class CardStrategy2 implements PaymentStrategy{
	/**
	 * 賬號信息
	 */
	private String account;
	public CardStrategy2(String account) {
		this.account = account;
	}
	//這個新的算法自己知道使用擴展的支付上下文,所以強制造型以下
	@Override
	public void pay(PaymentContext ctx) {
		PaymentContext2 ctx2 = (PaymentContext2)ctx;
		System.out.println("現在給 " + ctx2.getUserName() +"的 "+ account  +" 賬號支付了 "+ ctx2.getMoney()+"元");
		//連接銀行,進行轉賬
	}

}

8. 策略模式的注意事項和細節

  1. 策略模式的關鍵是:分析項目中變化部分與不變部分
  2. 策略模式的核心思想是:多用組合/聚合 少用繼承;用行爲類組合,而不是行爲的繼承。更有彈性
  3. 體現了“對修改關閉,對擴展開放”原則,客戶端增加行爲不用修改原有代碼,只要添加一種策略(或者行爲)
    即可,避免了使用多重轉移語句(if…else if…else)
  4. 提供了可以替換繼承關係的辦法: 策略模式將算法封裝在獨立的 Strategy 類中使得你可以獨立於其 Context 改
    變它,使它易於切換、易於理解、易於擴展
  5. 需要注意的是:每添加一個策略就要增加一個類,當策略過多是會導致類數目龐

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章