案例引入
- 有各種鴨子,比如野鴨、北京鴨、水鴨等。 鴨子有各種行為,比如走路、叫、飛行等。不同鴨子的行為可能略有不同。要求顯示鴨子的信息
傳統(tǒng)方案實現(xiàn)
不同的鴨子繼承一個父類Duck,如果是相同的行為就繼承,不同行為就重寫方法
實現(xiàn)
【鴨子抽象類】
package com.atguigu.strategy;
public abstract class Duck {
public Duck() {
}
/**
* 顯示鴨子信息
*/
public abstract void display();
/**
* 叫法
*/
public void quack() {
System.out.println("鴨子嘎嘎叫~~");
}
/**
* 游泳方法
*/
public void swim() {
System.out.println("鴨子會游泳~~");
}
/**
* 飛翔方法
*/
public void fly() {
System.out.println("鴨子會飛翔~~~");
}
}
【野鴨】
package com.atguigu.strategy;
public class WildDuck extends Duck {
@Override
public void display() {
System.out.println(" 這是野鴨 ");
}
}
【北京鴨】
package com.atguigu.strategy;
public class PekingDuck extends Duck {
@Override
public void display() {
System.out.println("~~北京鴨~~~");
}
/**
* 因為北京鴨不能飛翔,因此需要重寫fly
*/
@Override
public void fly() {
System.out.println("北京鴨不能飛翔");
}
}
【玩具鴨】
package com.atguigu.strategy;
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("玩具鴨不會飛翔~~~");
}
}
分析
- 缺點:因為繼承了Duck,所有鴨子都有了會飛的方法,雖然可以通過覆蓋fly方法來解決,但是,如果子類很多方法都不需要呢,如果每個都要去覆蓋一下就很麻煩了
- 改進:使用策略模式
介紹
基本介紹
- 策略模式中,定義算法族,分別封裝到不同的類中,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。使用策略模式可以整體地替換算法的實現(xiàn)部分,讓我們可以輕松地以不同的算法去解決同一個問題
- 該模式體現(xiàn)了幾個設(shè)計原則:把變化的代碼從不變的代碼中分離出來;針對接口編程而不是具體類(定義了策略接口);多用組合/聚合,少用繼承(客戶通過組合方式使用策略)
登場角色
Context聚合了策略接口,后面需要使用到哪個具體策略的方法,就傳入該具體策略的實例作為參數(shù)即可
-
Strategy(策略)
:Strategy角色負(fù)責(zé)定義實現(xiàn)策略所必需的接口(API) -
ConcreteStrategy(具體的策略)
:ConcreteStrategy角色負(fù)責(zé)實現(xiàn)Strategy角色的接口(API),即負(fù)責(zé)實現(xiàn)具體的策略(戰(zhàn)略、方向、方法和算法) -
Context(上下文)
:負(fù)責(zé)使用Strategy角色。Context角色保存了ConcreteStrategy角色的實例,并使用ConcreteStrategy角色(即調(diào)用Strategy角色的接口)去實現(xiàn)需求
案例實現(xiàn)
案例一
類圖
實現(xiàn)
【飛翔策略 FlyBehavior】
package com.atguigu.strategy.improve;
public interface FlyBehavior {
/**
* 讓子類具體實現(xiàn)
*/
void fly();
}
【叫策略 QuackBehavior】
package com.atguigu.strategy.improve;
public interface QuackBehavior {
void quack();
}
【飛翔技術(shù)高超:GoodFlyBehavior】
package com.atguigu.strategy.improve;
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println(" 飛翔技術(shù)高超 ~~~");
}
}
【飛翔技術(shù)一般:BadFlyBehavior 】
package com.atguigu.strategy.improve;
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println(" 飛翔技術(shù)一般 ");
}
}
【不會飛翔】
package com.atguigu.strategy.improve;
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println(" 不會飛翔 ");
}
}
【鴨子抽象類】
package com.atguigu.strategy.improve;
public abstract class Duck {
/**
* 策略接口 飛翔
*/
FlyBehavior flyBehavior;
/**
* 策略接口 叫
*/
QuackBehavior quackBehavior;
public Duck() {
}
/**
* 顯示鴨子信息
*/
public abstract void display();
public void quack() {
System.out.println("鴨子嘎嘎叫~~");
}
public void swim() {
System.out.println("鴨子會游泳~~");
}
public void fly() {
//改進
if(flyBehavior != null) {
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
【野鴨】
package com.atguigu.strategy.improve;
public class WildDuck extends Duck {
/**
* 構(gòu)造器,傳入FlyBehavor 的對象
*/
public WildDuck() {
// 野鴨飛翔技術(shù)較強
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println(" 這是野鴨 ");
}
}
【北京鴨】
package com.atguigu.strategy.improve;
public class PekingDuck extends Duck {
/**
* 假如北京鴨可以飛翔,但是飛翔技術(shù)一般
*/
public PekingDuck() {
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("~~北京鴨~~~");
}
}
【玩具鴨】
package com.atguigu.strategy.improve;
public class ToyDuck extends Duck {
public ToyDuck() {
// 玩具鴨不會飛翔
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
System.out.println("玩具鴨");
}
/**
* 需要重寫父類的所有方法
*/
public void quack() {
System.out.println("玩具鴨不能叫~~");
}
public void swim() {
System.out.println("玩具鴨不會游泳~~");
}
}
【主類】
package com.atguigu.strategy.improve;
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();
//可以動態(tài)改變某個對象的行為, 將北京鴨改成不能飛
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鴨的實際飛翔能力");
pekingDuck.fly();
}
}
【輸出】
飛翔技術(shù)高超 ~~~
不會飛翔
飛翔技術(shù)一般
北京鴨的實際飛翔能力
不會飛翔
Process finished with exit code 0
尖叫策略和飛翔策略的實現(xiàn)方式一樣,這里就不再實現(xiàn)了
案例二
類圖
實現(xiàn)
【手勢類:并不是策略模式的角色】
package com.atguigu.strategy.Sample;
/**
* 手勢
*/
public class Hand {
/**
* 表示石頭的值
*/
public static final int HANDVALUE_GUU = 0;
/**
* 表示剪刀的值
*/
public static final int HANDVALUE_CHO = 1;
/**
* 表示布的值
*/
public static final int HANDVALUE_PAA = 2;
/**
* 表示猜拳中3種手勢的實例
*/
public static final Hand[] hand = {
new Hand(HANDVALUE_GUU),
new Hand(HANDVALUE_CHO),
new Hand(HANDVALUE_PAA),
};
/**
* 表示猜拳中手勢所對應(yīng)的字符串
*/
private static final String[] name = {
"石頭", "剪刀", "布",
};
/**
* 表示猜拳中出的手勢的值
*/
private int handvalue;
private Hand(int handvalue) {
this.handvalue = handvalue;
}
/**
* 根據(jù)手勢的值獲取其對應(yīng)的實例,這是一種單例模式,每種手勢只有一個實例
*
* @param handvalue
* @return
*/
public static Hand getHand(int handvalue) {
return hand[handvalue];
}
/**
* 如果this勝了h則返回true
*
* @param h
* @return
*/
public boolean isStrongerThan(Hand h) {
return fight(h) == 1;
}
/**
* 如果this輸給了h則返回true
*
* @param h
* @return
*/
public boolean isWeakerThan(Hand h) {
return fight(h) == -1;
}
/**
* 計分:平0, 勝1, 負(fù)-1
*
* @param h
* @return
*/
private int fight(Hand h) {
if (this == h) {
return 0;
} else if ((this.handvalue + 1) % 3 == h.handvalue) {
// 當(dāng)(this.handvalue + 1) % 3 == h.handvalue時,可能得手勢組合如下
// this是石頭,h是剪刀
// this是剪刀,h是布
// this是布,h是石頭
return 1;
} else {
return -1;
}
}
/**
* 轉(zhuǎn)換為手勢值所對應(yīng)的字符串
*
* @return
*/
public String toString() {
return name[handvalue];
}
}
【策略接口】
package com.atguigu.strategy.Sample;
public interface Strategy {
/**
* 獲取下一局要出的手勢
* @return
*/
public abstract Hand nextHand();
/**
* 學(xué)習(xí)上一局的手勢是否獲勝了,獲勝就傳進來true,否則返回false
* @param win
*/
public abstract void study(boolean win);
}
【具體策略一】
package com.atguigu.strategy.Sample;
import java.util.Random;
/**
* 該策略是:如果上一局贏了,這局的手勢就和上一局的相同;如果上一局輸了,就隨機出
*/
public class WinningStrategy implements Strategy {
private Random random;
/**
* 保存上一局是贏還是輸了
*/
private boolean won = false;
/**
* 保存上一局出的手勢
*/
private Hand prevHand;
public WinningStrategy(int seed) {
random = new Random(seed);
}
public Hand nextHand() {
if (!won) {
prevHand = Hand.getHand(random.nextInt(3));
}
return prevHand;
}
public void study(boolean win) {
won = win;
}
}
【具體策略二】
package com.atguigu.strategy.Sample;
import java.util.Random;
public class ProbStrategy implements Strategy {
private Random random;
private int prevHandValue = 0;
private int currentHandValue = 0;
/**
* 過去的勝率:history[上一局出的手勢][這一局所出的手勢]
* 假設(shè)上一局出的手勢是石頭:
* history[0][0]:兩局分別出了石頭、石頭的獲勝次數(shù)
* history[0][1]:兩局分別出了石頭、剪刀的獲勝次數(shù)
* history[0][2]:兩局分別出了石頭、布的獲勝次數(shù)
* 若history[0][0]=3;history[0][1]=5;history[0][2]=7
* 下一把出什么?使用輪盤賭的方式,出石頭的概率是3/15;出剪刀的概率是5/15;出布的概率是7/15
*/
private int[][] history = {
{ 1, 1, 1, },
{ 1, 1, 1, },
{ 1, 1, 1, },
};
public ProbStrategy(int seed) {
random = new Random(seed);
}
/**
* 學(xué)習(xí)歷史勝率,根據(jù)輪盤賭的方式來出下一個手勢
* @return
*/
public Hand nextHand() {
int bet = random.nextInt(getSum(currentHandValue));
int handvalue = 0;
if (bet < history[currentHandValue][0]) {
handvalue = 0;
} else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
handvalue = 1;
} else {
handvalue = 2;
}
prevHandValue = currentHandValue;
currentHandValue = handvalue;
return Hand.getHand(handvalue);
}
/**
* 獲取第一把出hv,第二把出1、2、3的總次數(shù)
* @param hv
* @return
*/
private int getSum(int hv) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += history[hv][i];
}
return sum;
}
/**
* 學(xué)習(xí)經(jīng)驗,更新 history 表格
* @param win
*/
public void study(boolean win) {
if (win) {
history[prevHandValue][currentHandValue]++;
} else {
history[prevHandValue][(currentHandValue + 1) % 3]++;
history[prevHandValue][(currentHandValue + 2) % 3]++;
}
}
}
【游戲選手類】
package com.atguigu.strategy.Sample;
/**
* 玩猜拳游戲的選手類
*/
public class Player {
private String name;
/**
* 記錄選手要選用的策略
*/
private Strategy strategy;
/**
* 贏的局?jǐn)?shù)
*/
private int wincount;
/**
* 輸?shù)木謹(jǐn)?shù)
*/
private int losecount;
/**
* 總局?jǐn)?shù)
*/
private int gamecount;
/**
* 傳入選手的姓名和策略
*
* @param name
* @param strategy
*/
public Player(String name, Strategy strategy) {
this.name = name;
this.strategy = strategy;
}
/**
* 策略決定下一局要出的手勢
*
* @return
*/
public Hand nextHand() {
return strategy.nextHand();
}
/**
* 猜拳勝利
*/
public void win() {
strategy.study(true);
wincount++;
gamecount++;
}
/**
* 猜拳失敗
*/
public void lose() {
strategy.study(false);
losecount++;
gamecount++;
}
/**
* 猜拳平局
*/
public void even() {
gamecount++;
}
public String toString() {
return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
}
}
【主類】
package com.atguigu.strategy.Sample;
public class Main {
public static void main(String[] args) {
// 讓選手分別使用兩種策略來比試
Player player1 = new Player("Taro", new WinningStrategy(314));
Player player2 = new Player("Hana", new ProbStrategy(12));
for (int i = 0; i < 10000; i++) {
Hand nextHand1 = player1.nextHand();
Hand nextHand2 = player2.nextHand();
if (nextHand1.isStrongerThan(nextHand2)) {
// System.out.println("Winner:" + player1);
player1.win();
player2.lose();
} else if (nextHand2.isStrongerThan(nextHand1)) {
// System.out.println("Winner:" + player2);
player1.lose();
player2.win();
} else {
// System.out.println("Even...");
player1.even();
player2.even();
}
}
System.out.println("Total result:");
System.out.println(player1.toString());
System.out.println(player2.toString());
}
}
【運行】
Total result:
[Taro:10000 games, 3107 win, 3617 lose]
[Hana:10000 games, 3617 win, 3107 lose]
Process finished with exit code 0
問答
如果需要增加一個隨機出手勢的策略,需要怎么實現(xiàn)
答:在nextHand方法中使用隨機數(shù)即可,因為全部都是隨機的,不需要學(xué)習(xí)之前的經(jīng)驗,因此study方法可以是空方法
在示例程序中,Hand類的fight方法負(fù)責(zé)判斷平局。在進行判斷時,它使用的表達式不是
this.handValue == h.value
,而是this==h
,請問為什么可以這樣寫?
答:因為使用了單例模式,只有三個手勢實例,如果兩個手勢的handValue相同,說明兩個實例就是同一個實例
編寫Winningstrategy類時,won 字段的定義不是
private boolean won = false;
而是寫成了如下這樣private boolean won;
雖然寫法不同,但是兩者的運行結(jié)果一樣,為什么?
答:因為全局變量如果沒有被賦值就會被自動初始化:boolean類型默認(rèn)是false;數(shù)值類型默認(rèn)是0;引用類型默認(rèn)是null。注意,局部變量不會被自動初始化
策略模式在JDK源碼中的使用
簡單來說,就是在排序的時候,可以指定不同的排序策略
package com.atguigu.jdk;
import java.util.Arrays;
import java.util.Comparator;
public class Strategy {
public static void main(String[] args) {
// TODO Auto-generated method stub
//數(shù)組
Integer[] data = {9, 1, 2, 8, 4, 3};
// 實現(xiàn)降序排序,返回-1放左邊,1放右邊,0保持不變
// 說明
// 1. 實現(xiàn)了 Comparator 接口(策略接口) , 匿名類對象:new Comparator<Integer>(){..}
// 2. 對象 new Comparator<Integer>(){..} 就是實現(xiàn)了 策略接口 的對象
// 3. public int compare(Integer o1, Integer o2){} 指定具體的處理策略
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return -1;
} else {
return 1;
}
}
};
// sort源碼說明 傳入數(shù)字和一個排序策略
/*
* public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a); //默認(rèn)方法
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c); //使用策略對象c
else
// 使用策略對象c
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
*/
// 方式1
Arrays.sort(data, comparator);
// 降序排序
System.out.println(Arrays.toString(data));
//方式2- 同時lambda 表達式實現(xiàn) 策略模式
Integer[] data2 = {19, 11, 12, 18, 14, 13};
// 換一個排序策略
Arrays.sort(data2, (var1, var2) -> {
if (var1.compareTo(var2) > 0) {
return -1;
} else {
return 1;
}
});
System.out.println("data2=" + Arrays.toString(data2));
}
}
總結(jié)
【說明】
- 策略模式的關(guān)鍵是分析項目中變化部分與不變部分
【優(yōu)點】
- 策略模式的核心思想是:多用組合/聚合,少用繼承;用行為類來組合,而不是行為的繼承
- 體現(xiàn)了“對修改關(guān)閉,對擴展開放”原則,客戶端增加行為不用修改原有代碼,只要添加一種策略 (或者行為)即可,避免了使用多重判斷語句 (if…else if…else)
- 提供了可以替換繼承關(guān)系的辦法: 策略模式將算法封裝在獨立的Strategy類中使得你可以獨立于其他Context改變它,使它易于切換、易于理解、易于擴展
- 程序運行過程中也可以切換策略:如果使用Strategy模式,在程序運行中也可以切換ConcreteStrategy角色。如在內(nèi)存較少的環(huán)境使用一種算法,內(nèi)存較多的環(huán)境使用另外一種算法
【缺點】
- 每添加一個策略就要增加一個類,當(dāng)策略過多是會導(dǎo)致類數(shù)目龐大
【問答】
為什么需要特意編寫Strategy角色文章來源:http://www.zghlxwxcb.cn/news/detail-662925.html
答:當(dāng)我們想要通過改善算法來提高算法的處理速度時,如果使用了Strategy模式,就不必修改Strategy角色的接口(API)了,僅僅修改ConcreteStrategy角色即可。而且,使用委托這種弱關(guān)聯(lián)關(guān)系可以很方便地整體替換算法,這樣也更加方便算法的比較文章來源地址http://www.zghlxwxcb.cn/news/detail-662925.html
文章說明
- 本文章為本人學(xué)習(xí)尚硅谷的學(xué)習(xí)筆記,文章中大部分內(nèi)容來源于尚硅谷視頻(點擊學(xué)習(xí)尚硅谷相關(guān)課程),也有部分內(nèi)容來自于自己的思考,發(fā)布文章是想幫助其他學(xué)習(xí)的人更方便地整理自己的筆記或者直接通過文章學(xué)習(xí)相關(guān)知識,如有侵權(quán)請聯(lián)系刪除,最后對尚硅谷的優(yōu)質(zhì)課程表示感謝。
- 本人還同步閱讀《圖解設(shè)計模式》書籍(圖解設(shè)計模式/(日)結(jié)城浩著;楊文軒譯–北京:人民郵電出版社,2017.1),進而綜合兩者的內(nèi)容,讓知識點更加全面
到了這里,關(guān)于【設(shè)計模式——學(xué)習(xí)筆記】23種設(shè)計模式——策略模式Strategy(原理講解+應(yīng)用場景介紹+案例介紹+Java代碼實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!