行為設計—策略設計模式詳解——小馬同學@Tian

小馬同學@Tian發表於2020-11-30

策略模式(策略設計模式)詳解

在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。

在軟體開發中也常常遇到類似的情況,當實現某一個功能存在多種演算法或者策略,我們可以根據環境或者條件的不同選擇不同的演算法或者策略來完成該功能,如資料排序策略有氣泡排序、選擇排序、插入排序、二叉樹排序等。

如果使用多重條件轉移語句實現(即硬編碼),不但使條件語句變得很複雜,而且增加、刪除或更換演算法要修改原始碼,不易維護,違背開閉原則。如果採用策略模式就能很好解決該問題。

策略模式的定義與特點

策略(Strategy)模式的定義:該模式定義了一系列演算法,並將每個演算法封裝起來,使它們可以相互替換,且演算法的變化不會影響使用演算法的客戶。策略模式屬於物件行為模式,它通過對演算法進行封裝,把使用演算法的責任和演算法的實現分割開來,並委派給不同的物件對這些演算法進行管理。

策略模式的主要優點如下。

1. 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句,如 if...else 語句、switch...case 語句。
2. 策略模式提供了一系列的可供重用的演算法族,恰當使用繼承可以把演算法族的公共程式碼轉移到父類裡面,從而避免重複的程式碼。
3. 策略模式可以提供相同行為的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
4. 策略模式提供了對開閉原則的完美支援,可以在不修改原始碼的情況下,靈活增加新演算法。
5. 策略模式把演算法的使用放到環境類中,而演算法的實現移到具體策略類中,實現了二者的分離。

其主要缺點如下。

1. 客戶端必須理解所有策略演算法的區別,以便適時選擇恰當的演算法類。
2. 策略模式造成很多的策略類,增加維護難度。

策略模式的結構與實現

策略模式是準備一組演算法,並將這組演算法封裝到一系列的策略類裡面,作為一個抽象策略類的子類。策略模式的重心不是如何實現演算法,而是如何組織這些演算法,從而讓程式結構更加靈活,具有更好的維護性和擴充套件性,現在我們來分析其基本結構和實現方法。

1. 模式的結構

策略模式的主要角色如下。

1. 抽象策略(Strategy)類:定義了一個公共介面,各種不同的演算法以不同的方式實現這個介面,環境角色使用這個介面呼叫不同的演算法,一般使用介面或抽象類實現。
2. 具體策略(Concrete Strategy)類:實現了抽象策略定義的介面,提供具體的演算法實現。
3. 環境(Context)類:持有一個策略類的引用,最終給客戶端呼叫。

其結構圖如圖 1 所示。

策略模式的結構圖
圖1 策略模式的結構圖

2. 模式的實現

策略模式的實現程式碼如下:

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的策略方法被訪問!

策略模式的應用例項

【例1】策略模式在“大閘蟹”做菜中的應用。

分析:關於大閘蟹的做法有很多種,我們以清蒸大閘蟹和紅燒大閘蟹兩種方法為例,介紹策略模式的應用。

首先,定義一個大閘蟹加工的抽象策略類(CrabCooking),裡面包含了一個做菜的抽象方法 CookingMethod();然後,定義清蒸大閘蟹(SteamedCrabs)和紅燒大閘蟹(BraisedCrabs)的具體策略類,它們實現了抽象策略類中的抽象方法;由於本程式要顯示做好的結果圖,所以將具體策略類定義成 JLabel 的子類;最後,定義一個廚房(Kitchen)環境類,它具有設定和選擇做菜策略的方法;客戶類通過廚房類獲取做菜策略,並把做菜結果圖在窗體中顯示出來,圖 2 所示是其結構圖。

大閘蟹做菜策略的結構圖
圖2 大閘蟹做菜策略的結構圖

程式程式碼如下:

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();    //做菜      
    }
}

程式執行結果如圖 3 所示。

大閘蟹做菜結果
圖3 大閘蟹做菜結果

【例2】用策略模式實現從韶關去婺源旅遊的出行方式。

分析:從韶關去婺源旅遊有以下幾種出行方式:坐火車、坐汽車和自駕車,所以該例項用策略模式比較適合,圖 4 所示是其結構圖。

婺源旅遊結構圖
圖4 婺源旅遊結構圖

策略模式的應用場景

策略模式在很多地方用到,如 Java SE 中的容器佈局管理就是一個典型的例項,Java SE 中的每個容器都存在多種佈局供使用者選擇。在程式設計中,通常在以下幾種情況中使用策略模式較多。

1. 一個系統需要動態地在幾種演算法中選擇一種時,可將每個演算法封裝到策略類中。
2. 一個類定義了多種行為,並且這些行為在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
3. 系統中各演算法彼此完全獨立,且要求對客戶隱藏具體演算法的實現細節時。
4. 系統要求使用演算法的客戶不應該知道其操作的資料時,可使用策略模式來隱藏與演算法相關的資料結構。
5. 多個類只區別在表現行為不同,可以使用策略模式,在執行時動態選擇具體要執行的行為。

策略模式的擴充套件

在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略演算法將變得很複雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作複雜度,其結構圖如圖 5 所示。

策略工廠模式的結構圖
使用演算法的客戶不應該知道其操作的資料時,可使用策略模式來隱藏與演算法相關的資料結構。
5. 多個類只區別在表現行為不同,可以使用策略模式,在執行時動態選擇具體要執行的行為。


## 策略模式的擴充套件

在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略演算法將變得很複雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作複雜度,其結構圖如圖 5 所示。



[外鏈圖片轉存中...(img-TI5ckz0T-1606732889458)]
圖5 策略工廠模式的結構圖

相關文章