設計模式之策略(Strategy)模式
在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。
在軟件開發中也常常遇到類似的情況,當實現某一個功能存在多種算法或者策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能,如數據排序策略有冒泡排序、選擇排序、插入排序、二叉樹排序等。
如果使用多重條件轉移語句實現(即硬編碼),不但使條件語句變得很複雜,而且增加、刪除或更換算法要修改原代碼,不易維護,違背開閉原則。如果採用策略模式就能很好解決該問題。
定義與特點
策略(Strategy)模式的定義:該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。策略模式屬於對象行爲模式,它通過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不同的對象對這些算法進行管理。
策略模式的主要優點如下。
- 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句。
- 策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裏面,從而避免重複的代碼。
- 策略模式可以提供相同行爲的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
- 策略模式提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
- 策略模式把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。
其主要缺點如下。
- 客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
- 策略模式造成很多的策略類。
結構與實現
策略模式是準備一組算法,並將這組算法封裝到一系列的策略類裏面,作爲一個抽象策略類的子類。策略模式的重心不是如何實現算法,而是如何組織這些算法,從而讓程序結構更加靈活,具有更好的維護性和擴展性,現在我們來分析其基本結構和實現方法。
結構
策略模式的主要角色如下。
- 抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現。
- 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現。
- 環境(Context)類:持有一個策略類的引用,最終給客戶端調用。
其結構圖如圖 1 所示。
實現
策略模式的實現代碼如下:
package com.design.pattern.behaviorPattern;
public class StrategyPattern {
public static void main(String[] args) {
Context context = new Context();
Strategy strategy = new ConcreteStrategyA();
context.setStrategy(strategy);
context.strategyMethod();
System.out.println("------------------");
strategy = new ConcreteStrategyB();
context.setStrategy(strategy);
context.strategyMethod();
}
}
//抽象策略類
interface Strategy{
public void strategyMethod();
}
//具體策略類A
class ConcreteStrategyA implements Strategy{
@Override
public void strategyMethod() {
System.out.println("具體策略A的策略方法被訪問!");
}
}
//具體策略類B
class ConcreteStrategyB implements Strategy{
@Override
public void strategyMethod() {
System.out.println("具體策略B的策略方法被訪問!");
}
}
class Context{
private Strategy strategy;
public void strategyMethod(){
strategy.strategyMethod();
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
}
應用實例
【例1】策略模式在“大閘蟹”做菜中的應用。
分析:關於大閘蟹的做法有很多種,我們以清蒸大閘蟹和紅燒大閘蟹兩種方法爲例,介紹策略模式的應用。
首先,定義一個大閘蟹加工的抽象策略類(CrabCooking),裏面包含了一個做菜的抽象方法 CookingMethod();然後,定義清蒸大閘蟹(SteamedCrabs)和紅燒大閘蟹(BraisedCrabs)的具體策略類,它們實現了抽象策略類中的抽象方法;由於本程序要顯示做好的結果圖(點此下載要顯示的結果圖),所以將具體策略類定義成 JLabel 的子類;最後,定義一個廚房(Kitchen)環境類,它具有設置和選擇做菜策略的方法;客戶類通過廚房類獲取做菜策略,並把做菜結果圖在窗體中顯示出來,圖 2 所示是其結構圖。
程序代碼如下:
package com.design.pattern.behaviorPattern.StrategyPattern;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
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{
@Override
public void cookingMethod() {
this.setIcon(new ImageIcon("D:\\IdeaProjects\\DesignPattern\\src\\resources\\picture\\SteamedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//具體策略類:紅燒大閘蟹
class BraisedCrabs extends JLabel implements CrabCooking {
@Override
public void cookingMethod() {
this.setIcon(new ImageIcon("D:\\IdeaProjects\\DesignPattern\\src\\resources\\picture\\BraisedCrabs.jpg"));
this.setHorizontalAlignment(CENTER);
}
}
//環境類:廚房
class Kitchen{
private CrabCooking strategy;
public CrabCooking getStrategy(){
return strategy;
}
public void setStrategy(CrabCooking strategy){
this.strategy = strategy;
}
public void CookingMethod(){
strategy.cookingMethod(); //做菜
}
}
程序運行結果如圖 3 所示。
【例2】用策略模式實現從韶關去婺源旅遊的出行方式。
分析:從韶關去婺源旅遊有以下幾種出行方式:坐火車、坐汽車和自駕車,所以該實例用策略模式比較適合,圖 4 所示是其結構圖。
應用場景
策略模式在很多地方用到,如 Java SE 中的容器佈局管理就是一個典型的實例,Java SE 中的每個容器都存在多種佈局供用戶選擇。在程序設計中,通常在以下幾種情況中使用策略模式較多。
- 一個系統需要動態地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。
- 一個類定義了多種行爲,並且這些行爲在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
- 系統中各算法彼此完全獨立,且要求對客戶隱藏具體算法的實現細節時。
- 系統要求使用算法的客戶不應該知道其操作的數據時,可使用策略模式來隱藏與算法相關的數據結構。
- 多個類只區別在表現行爲不同,可以使用策略模式,在運行時動態選擇具體要執行的行爲。
模式的擴展
在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略算法將變得很複雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作複雜度,其結構圖如圖 5 所示。
package com.design.pattern.behaviorPattern;
import java.util.HashMap;
import java.util.Map;
public class StrategyPattern {
public static void main(String[] args) {
Context context = new Context();
// Strategy strategy = new ConcreteStrategyA();
// context.setStrategy(strategy);
// context.strategyMethod();
// System.out.println("------------------");
// strategy = new ConcreteStrategyB();
// context.setStrategy(strategy);
// context.strategyMethod();
StrategyFactory strategyFactory = new StrategyFactory();
ConcreteStrategyA strategyA = (ConcreteStrategyA) strategyFactory.get("StrategyA");
context.setStrategy(strategyA);
context.strategyMethod();
}
}
//抽象策略類
interface Strategy{
public void strategyMethod();
}
//具體策略類A
class ConcreteStrategyA implements Strategy{
@Override
public void strategyMethod() {
System.out.println("具體策略A的策略方法被訪問!");
}
}
//具體策略類B
class ConcreteStrategyB implements Strategy{
@Override
public void strategyMethod() {
System.out.println("具體策略B的策略方法被訪問!");
}
}
class Context{
private Strategy strategy;
public void strategyMethod(){
strategy.strategyMethod();
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
}
//策略工廠
class StrategyFactory{
private Map<String,Strategy> strategys = new HashMap<>();
public StrategyFactory() {
strategys.put("StrategyA",new ConcreteStrategyA());
strategys.put("StrategyB",new ConcreteStrategyB());
}
public void put(String key, Strategy strategy){
strategys.put(key,strategy);
}
public Strategy get(String key){
return strategys.get(key);
}
public void strategyMethod(String key){
strategys.get(key).strategyMethod();
}
}