1、編寫鴨子項目,具體要求如下:
-
有各種鴨子(比如 野鴨、北京鴨、水鴨等, 鴨子有各種行爲,比如 叫、飛行等)
-
顯示鴨子的信息
2 傳統方案解決鴨子問題的分析和代碼實現
- 傳統的設計方案(類圖)
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.傳統的方式實現的問題分析和解決方案
-
其它鴨子,都繼承了 Duck 類,所以 fly 讓所有子類都會飛了,這是不正確的
-
上面說的 1 的問題,其實是繼承帶來的問題:對類的局部改動,尤其超類的局部改動,會影響其他部分。會有溢出效應
-
爲了改進 1 問題,我們可以通過覆蓋 fly 方法來解決 => 覆蓋解決
-
問題又來了,如果我們有一個玩具鴨子 ToyDuck, 這樣就需要 ToyDuck 去覆蓋 Duck 法 的所有實現的方法 => 解決思路 -》 式 策略模式 (strategy pattern)
4. 策略模式基本介紹
-
策略模式(Strategy Pattern)中,定義 算法族(策略組),分別封裝起來,讓他們之間可以互相替換,此模式讓 算法的變化獨立於 使用算法的客戶
-
這算法體現了幾個設計原則,第一、把變化的代碼從不變的代碼中分離出來;第二、針對接口編程而不是具體類(定義了策略接口);第三、多用組合/聚合,少用繼承(客戶通過組合方式使用策略)
5. 策略模式的原理類圖
說明:從上圖可以看到,客戶 context 有成員變量 strategy 或者其他的策略接口
,至於需要使用到哪個策略,我們可以在構造器中指定
6. 策略模式解決鴨子問題
- 應用實例要求
編寫程序完成前面的鴨子項目,要求使用策略模式 - 思路分析(類圖)
策略模式:分別封裝行爲接口,實現算法族,超類裏放行爲接口對象,在子類裏具體設定行爲對象。原則就是:
分離變化部分,封裝接口,基於接口編程各種功能。此模式讓行爲的變化獨立於算法的使用者
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 應用的源碼分析
-
JDK 的 Arrays 的 Comparator 就使用了策略模式
-
代碼分析+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()+" 元");
}
}
- 支付上下文的實現,是需要知道具體使用哪一個支付策略的,一般由客戶端來確定具體使用哪一種具體的策略,然後上下文去 真正執行。因此,這個上下文需要持有一個支付策略,而且是由客戶端來配置它。示例代碼如下:
/**
* 支付工資的上下文,每個人的工資不同,支付方式也不同
* @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. 策略模式的注意事項和細節
- 策略模式的關鍵是:分析項目中變化部分與不變部分
- 策略模式的核心思想是:多用組合/聚合 少用繼承;用行爲類組合,而不是行爲的繼承。更有彈性
- 體現了“對修改關閉,對擴展開放”原則,客戶端增加行爲不用修改原有代碼,只要添加一種策略(或者行爲)
即可,避免了使用多重轉移語句(if…else if…else) - 提供了可以替換繼承關係的辦法: 策略模式將算法封裝在獨立的 Strategy 類中使得你可以獨立於其 Context 改
變它,使它易於切換、易於理解、易於擴展 - 需要注意的是:每添加一個策略就要增加一個類,當策略過多是會導致類數目龐