前言
在現實生活中常常遇到實作某種目标存在多種政策可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。
在軟體開發中也常常遇到類似的情況,當實作某一個功能存在多種算法或者政策,我們可以根據環境或者條件的不同選擇不同的算法或者政策來完成該功能,如資料排序政策有冒泡排序、選擇排序、插入排序、二叉樹排序等。
如果使用多重條件轉移語句實作(即寫死),不但使條件語句變得很複雜,而且增加、删除或更換算法要修改原代碼,不易維護,違背開閉原則。如果采用政策模式就能很好解決該問題。
1. 定義與特點
政策模式(Strategy Pattern)的定義:該模式定義了一系列算法,并将每個算法封裝起來,使它們可以互相替換,且算法的變化不會影響使用算法的客戶。政策模式屬于對象行為模式,它通過對算法進行封裝,把使用算法的責任和算法的實作分割開來,并委派給不同的對象對這些算法進行管理。
優點:
- 多重條件語句不易維護,而使用政策模式可以避免使用多重條件語句,如 if…else 語句、switch…case 語句。
- 政策模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裡面,進而避免重複的代碼。
- 政策模式可以提供相同行為的不同實作,客戶可以根據不同時間或空間要求選擇不同的。
- 政策模式提供了對開閉原則的完美支援,可以在不修改原代碼的情況下,靈活增加新算法。
- 政策模式把算法的使用放到環境類中,而算法的實作移到具體政策類中,實作了二者的分離。
缺點:
- 用戶端必須了解所有政策算法的差別,以便适時選擇恰當的算法類。
- 政策模式造成很多的政策類,增加維護難度。
2. 應用場景
政策模式在很多地方用到,如 Java SE 中的容器布局管理就是一個典型的執行個體,Java SE 中的每個容器都存在多種布局供使用者選擇。在程式設計中,通常在以下幾種情況中使用政策模式較多。
- 一個系統需要動态地在幾種算法中選擇一種時,可将每個算法封裝到政策類中。
- 一個類定義了多種行為,并且這些行為在這個類的操作中以多個條件語句的形式出現,可将每個條件分支移入它們各自的政策類中以代替這些條件語句。
- 系統中各算法彼此完全獨立,且要求對客戶隐藏具體算法的實作細節時。
- 系統要求使用算法的客戶不應該知道其操作的資料時,可使用政策模式來隐藏與算法相關的資料結構。
- 多個類隻差別在表現行為不同,可以使用政策模式,在運作時動态選擇具體要執行的行為。
3. 結構與實作
政策模式是準備一組算法,并将這組算法封裝到一系列的政策類裡面,作為一個抽象政策類的子類。政策模式的重心不是如何實作算法,而是如何組織這些算法,進而讓程式結構更加靈活,具有更好的維護性和擴充性,現在我們來分析其基本結構和實作方法。
3.1 結構
政策模式的主要角色如下。
- 抽象政策(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實作這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實作。
- 具體政策(Concrete Strategy)類:實作了抽象政策定義的接口,提供具體的算法實作。
- 環境(Context)類:持有一個政策類的引用,最終給用戶端調用。 實作代碼如下:
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的政策方法被通路!");
}
}
//環境類
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
運作結果如下:
具體政策A的政策方法被通路!
-----------------
具體政策B的政策方法被通路!
4. 執行個體
【例1】政策模式在“大閘蟹”做菜中的應用。
分析:關于大閘蟹的做法有很多種,我們以清蒸大閘蟹和紅燒大閘蟹兩種方法為例,介紹政策模式的應用。
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("政策模式在大閘蟹做菜中的應用");
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);
}
}
//環境類:廚房
class Kitchen {
private CrabCooking strategy; //抽象政策
public void setStrategy(CrabCooking strategy) {
this.strategy = strategy;
}
public CrabCooking getStrategy() {
return strategy;
}
public void CookingMethod() {
strategy.CookingMethod(); //做菜
}
}