在現(xiàn)實生活中常常遇到實現(xiàn)某種目標(biāo)存在多種策略可供選擇的情況谣沸,例如刷钢,出行旅游可以乘坐飛機、乘坐火車乳附、騎自行車或自己開私家車等内地,超市促銷可以釆用打折、送商品许溅、送積分等方法瓤鼻。
在軟件開發(fā)中也常常遇到類似的情況,當(dāng)實現(xiàn)某一個功能存在多種算法或者策略贤重,我們可以根據(jù)環(huán)境或者條件的不同選擇不同的算法或者策略來完成該功能茬祷,如數(shù)據(jù)排序策略有冒泡排序、選擇排序并蝗、插入排序祭犯、二叉樹排序等。
如果使用多重條件轉(zhuǎn)移語句實現(xiàn)(即硬編碼)滚停,不但使條件語句變得很復(fù)雜沃粗,而且增加、刪除或更換算法要修改原代碼键畴,不易維護最盅,違背開閉原則。如果采用策略模式就能很好解決該問題起惕。
策略模式的定義與特點
策略(Strategy)模式的定義:該模式定義了一系列算法涡贱,并將每個算法封裝起來,使它們可以相互替換惹想,且算法的變化不會影響使用算法的客戶问词。策略模式屬于對象行為模式,它通過對算法進行封裝嘀粱,把使用算法的責(zé)任和算法的實現(xiàn)分割開來激挪,并委派給不同的對象對這些算法進行管理。
策略模式的主要優(yōu)點如下锋叨。
- 多重條件語句不易維護垄分,而使用策略模式可以避免使用多重條件語句,如 if...else 語句娃磺、switch...case 語句锋喜。
- 策略模式提供了一系列的可供重用的算法族,恰當(dāng)使用繼承可以把算法族的公共代碼轉(zhuǎn)移到父類里面,從而避免重復(fù)的代碼嘿般。
- 策略模式可以提供相同行為的不同實現(xiàn)段标,客戶可以根據(jù)不同時間或空間要求選擇不同的。
- 策略模式提供了對開閉原則的完美支持炉奴,可以在不修改原代碼的情況下逼庞,靈活增加新算法。
- 策略模式把算法的使用放到環(huán)境類中瞻赶,而算法的實現(xiàn)移到具體策略類中赛糟,實現(xiàn)了二者的分離。
其主要缺點如下砸逊。
- 客戶端必須理解所有策略算法的區(qū)別璧南,以便適時選擇恰當(dāng)?shù)乃惴悺?/li>
- 策略模式造成很多的策略類,增加維護難度师逸。
策略模式的結(jié)構(gòu)與實現(xiàn)
策略模式是準(zhǔn)備一組算法司倚,并將這組算法封裝到一系列的策略類里面,作為一個抽象策略類的子類篓像。策略模式的重心不是如何實現(xiàn)算法动知,而是如何組織這些算法,從而讓程序結(jié)構(gòu)更加靈活员辩,具有更好的維護性和擴展性盒粮,現(xiàn)在我們來分析其基本結(jié)構(gòu)和實現(xiàn)方法。
1. 模式的結(jié)構(gòu)
策略模式的主要角色如下奠滑。
- 抽象策略(Strategy)類:定義了一個公共接口丹皱,各種不同的算法以不同的方式實現(xiàn)這個接口,環(huán)境角色使用這個接口調(diào)用不同的算法宋税,一般使用接口或抽象類實現(xiàn)摊崭。
- 具體策略(Concrete Strategy)類:實現(xiàn)了抽象策略定義的接口,提供具體的算法實現(xiàn)弃甥。
- 環(huán)境(Context)類:持有一個策略類的引用爽室,最終給客戶端調(diào)用汁讼。
其結(jié)構(gòu)圖如圖 1 所示淆攻。
圖1 策略模式的結(jié)構(gòu)圖
2. 模式的實現(xiàn)
策略模式的實現(xiàn)代碼如下:
public class StrategyPattern {
public static void main(String[] args) {
Context c = new Context();
Strategy s = new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s = new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
//抽象策略類
interface Strategy {
public void strategyMethod(); //策略方法
}
//具體策略類A
class ConcreteStrategyA implements Strategy {
public void strategyMethod() {
System.out.println("具體策略A的策略方法被訪問!");
}
}
//具體策略類B
class ConcreteStrategyB implements Strategy {
public void strategyMethod() {
System.out.println("具體策略B的策略方法被訪問嘿架!");
}
}
//環(huán)境類
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
程序運行結(jié)果如下:
具體策略A的策略方法被訪問瓶珊!
-----------------
具體策略B的策略方法被訪問!
策略模式的應(yīng)用實例
【例1】策略模式在“大閘蟹”做菜中的應(yīng)用耸彪。
分析:關(guān)于大閘蟹的做法有很多種伞芹,我們以清蒸大閘蟹和紅燒大閘蟹兩種方法為例,介紹策略模式的應(yīng)用。
首先唱较,定義一個大閘蟹加工的抽象策略類(CrabCooking)扎唾,里面包含了一個做菜的抽象方法 CookingMethod();然后南缓,定義清蒸大閘蟹(SteamedCrabs)和紅燒大閘蟹(BraisedCrabs)的具體策略類胸遇,它們實現(xiàn)了抽象策略類中的抽象方法;由于本程序要顯示做好的結(jié)果圖汉形,所以將具體策略類定義成 JLabel 的子類纸镊;最后,定義一個廚房(Kitchen)環(huán)境類概疆,它具有設(shè)置和選擇做菜策略的方法逗威;客戶類通過廚房類獲取做菜策略,并把做菜結(jié)果圖在窗體中顯示出來岔冀,圖 2 所示是其結(jié)構(gòu)圖凯旭。
圖2 大閘蟹做菜策略的結(jié)構(gòu)圖
程序代碼如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CrabCookingStrategy implements ItemListener {
private JFrame f;
private JRadioButton qz, hs;
private JPanel CenterJP, SouthJP;
private Kitchen cf; //廚房
private CrabCooking qzx, hsx; //大閘蟹加工者
CrabCookingStrategy() {
f = new JFrame("策略模式在大閘蟹做菜中的應(yīng)用");
f.setBounds(100, 100, 500, 400);
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SouthJP = new JPanel();
CenterJP = new JPanel();
f.add("South", SouthJP);
f.add("Center", CenterJP);
qz = new JRadioButton("清蒸大閘蟹");
hs = new JRadioButton("紅燒大閘蟹");
qz.addItemListener(this);
hs.addItemListener(this);
ButtonGroup group = new ButtonGroup();
group.add(qz);
group.add(hs);
SouthJP.add(qz);
SouthJP.add(hs);
//---------------------------------
cf = new Kitchen(); //廚房
qzx = new SteamedCrabs(); //清蒸大閘蟹類
hsx = new BraisedCrabs(); //紅燒大閘蟹類
}
public void itemStateChanged(ItemEvent e) {
JRadioButton jc = (JRadioButton) e.getSource();
if (jc == qz) {
cf.setStrategy(qzx);
cf.CookingMethod(); //清蒸
} else if (jc == hs) {
cf.setStrategy(hsx);
cf.CookingMethod(); //紅燒
}
CenterJP.removeAll();
CenterJP.repaint();
CenterJP.add((Component) cf.getStrategy());
f.setVisible(true);
}
public static void main(String[] args) {
new CrabCookingStrategy();
}
}
//抽象策略類:大閘蟹加工類
interface CrabCooking {
public void CookingMethod(); //做菜方法
}
//具體策略類:清蒸大閘蟹
class SteamedCrabs extends JLabel implements CrabCooking {
private static final long serialVersionUID = 1L;
public void CookingMethod() {
this.setIcon(new ImageIcon("src/strategy/SteamedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//具體策略類:紅燒大閘蟹
class BraisedCrabs extends JLabel implements CrabCooking {
private static final long serialVersionUID = 1L;
public void CookingMethod() {
this.setIcon(new ImageIcon("src/strategy/BraisedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//環(huán)境類:廚房
class Kitchen {
private CrabCooking strategy; //抽象策略
public void setStrategy(CrabCooking strategy) {
this.strategy = strategy;
}
public CrabCooking getStrategy() {
return strategy;
}
public void CookingMethod() {
strategy.CookingMethod(); //做菜
}
}
程序運行結(jié)果如圖 3 所示。
圖3 大閘蟹做菜結(jié)果
【例2】用策略模式實現(xiàn)從韶關(guān)去婺源旅游的出行方式楣颠。
分析:從韶關(guān)去婺源旅游有以下幾種出行方式:坐火車尽纽、坐汽車和自駕車,所以該實例用策略模式比較適合童漩,圖 4 所示是其結(jié)構(gòu)圖弄贿。
圖4 婺源旅游結(jié)構(gòu)圖
策略模式的應(yīng)用場景
策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一個典型的實例矫膨,Java SE 中的每個容器都存在多種布局供用戶選擇差凹。在程序設(shè)計中,通常在以下幾種情況中使用策略模式較多侧馅。
- 一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種時危尿,可將每個算法封裝到策略類中。
- 一個類定義了多種行為馁痴,并且這些行為在這個類的操作中以多個條件語句的形式出現(xiàn)谊娇,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
- 系統(tǒng)中各算法彼此完全獨立罗晕,且要求對客戶隱藏具體算法的實現(xiàn)細節(jié)時济欢。
- 系統(tǒng)要求使用算法的客戶不應(yīng)該知道其操作的數(shù)據(jù)時,可使用策略模式來隱藏與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu)小渊。
- 多個類只區(qū)別在表現(xiàn)行為不同法褥,可以使用策略模式,在運行時動態(tài)選擇具體要執(zhí)行的行為酬屉。
策略模式的擴展
在一個使用策略模式的系統(tǒng)中半等,當(dāng)存在的策略很多時揍愁,客戶端管理所有策略算法將變得很復(fù)雜,如果在環(huán)境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作復(fù)雜度杀饵,其結(jié)構(gòu)圖如圖 5 所示莽囤。
圖5 策略工廠模式的結(jié)構(gòu)圖