設計模式-策略模式Strategy以及消滅if else


概述

  如果在開發過程中,出現大量的if else或者switch case 語句,如果這些語句塊中的代碼並不是包含業務邏輯,只是單純的分流方法,那么,每一個語句塊中都是一個算法或者叫策略

背景

 

  比如在最近項目中遇到的問題。一個二維碼字符串解析的方法:

    微信的二維碼掃描結果包含“WeChat”,解析規則是拿着文本到微信服務器解析,返回解析對象

    支付寶二維碼掃描結果包含“Alipay”,解析規則是使用“->”分割字符串得到解析對象。

  最簡單快速的代碼就是直接if else判斷:

  

 1         /// <summary>
 2         /// 解析方法
 3         /// </summary>
 4         /// <param name="text">掃描得到的文本</param>
 5         public void AnalysisAction(string text)
 6         {
 7             //微信解析方法
 8             if (text.Contains("WeChat"))
 9             {
10                 //拿着text到微信服務器解析,返回解析對象。
11             }
12             //支付寶解析方法
13             else if (text.Contains("Alipay"))
14             {
15                 //使用->分割,得到解析對象。
16             }
17         }

 

 

 問題

  當然使用這種方式是可以的,但是如果以后又加入一種掃碼解析方法:

  中國聯通二維碼掃描文本中包含“Unicom”,解析規則為以“:”分割,得到解析對象。

  那么你就要繼續添加else if(text.Contains("Unicom"))。每次增加一個新的掃描解析規則,你都要去增加else if判斷,這種是面向過程的體驗,屬於硬編碼。這也違反了面向對象的開閉原則。

改進(抽象)

  我們可以使用策略模式來改進代碼。義一系列的算法,把每一個算法封裝起來, 並且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。

  抽象出一個掃描解析接口,定義一個解析方法。然后分別定義微信和支付寶的解析方法集成接口。

  策略模式的uml圖如下:

  

 
    /// <summary>
    /// 掃描解析規則抽象接口
    /// </summary>
    public interface IStrategy
    {
        /// <summary>
        /// 掃描解析方法策略
        /// </summary>
        /// <param name="text"></param>
        void AnalysisAction(string text);
    }
掃描解析規則抽象接口
    /// <summary>
    /// 微信掃描解析規則
    /// </summary>
    public class StrategyWeChat : IStrategy
    {
        /// <summary>
        /// 掃描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction(string text)
        {
            //拿着text到微信服務器解析,返回解析對象。
        }
    }
微信掃描策略
    /// <summary>
    /// 支付寶掃描解析規則
    /// </summary>
    public class StrategyAlipay : IStrategy
    {
        /// <summary>
        /// 掃描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction(string text)
        {
            //使用->分割,得到解析對象。
        }
    }
支付寶掃描解析策略
    /// <summary>
    /// 接口管理類
    /// </summary>
    public class StrategyContext
    {
        private IStrategy strategy;
        /// <summary>
        /// 外層調用的時候決定使用哪個掃描策略
        /// </summary>
        /// <param name="strategy"></param>
        public StrategyContext(IStrategy strategy)
        {
            this.strategy = strategy;
        }
        public void AnalysisAction(string text)
        {
            strategy.AnalysisAction(text);
        }
    }
管理類

這樣我們的業務邏輯就可以這樣寫:

        private StrategyContext Context;
        /// <summary>
        /// 解析方法
        /// </summary>
        /// <param name="text">掃描得到的文本</param>
        public void AnalysisAction(string text)
        {

            //微信解析方法
            if (text.Contains("WeChat"))
            {
                Context = new StrategyContext(new StrategyWeChat());
            }
            //支付寶解析方法
            else if (text.Contains("Alipay"))
            {
                Context = new StrategyContext(new StrategyAlipay());
            }
            Context.AnalysisAction(text);
        }

  我們將具體的解析規則放到了具體的實現類中,但是我們並沒有消滅If else,如果在添加聯通的掃碼解析的話,還是需要修改代碼,添加else。

  這就是策略模式的缺點,必須知道要使用的具體的策略,也就是有的人說的還是要使用if else。

升級改造

  因為前面說到了策略模式的缺點。如果就是要消滅if else呢?我們可以將決定使用策略的決定權放到具體策略實現類中。

 1     /// <summary>
 2     /// 掃描解析規則抽象接口
 3     /// </summary>
 4     public interface IStrategy
 5     {
 6         /// <summary>
 7         /// 是否可以解析
 8         /// </summary>
 9         bool Analysisable { get; }
10 
11         /// <summary>
12         /// 掃描解析方法策略
13         /// </summary>
14         /// <param name="text"></param>
15         void AnalysisAction();
16     }
掃描解析規則抽象接口添加是否可以解析屬性
    /// <summary>
    /// 微信掃描解析規則
    /// </summary>
    public class StrategyWeChat : IStrategy
    {
        private string _text;
        public StrategyWeChat(string text)
        {
            this._text = text;
        }
        public bool Analysisable
        {
            get { return _text.Contains("WeChat"); }
        }

        /// <summary>
        /// 掃描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction()
        {
            //拿着_text到微信服務器解析,返回解析對象。
        }
    }
微信
    /// <summary>
    /// 支付寶掃描解析規則
    /// </summary>
    public class StrategyAlipay : IStrategy
    {
        private string _text;
        public StrategyAlipay(string text)
        {
            this._text = text;
        }
        public bool Analysisable
        {
            get { return _text.Contains("Alipay"); }
        }
        /// <summary>
        /// 掃描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction()
        {
            //使用->分割,得到解析對象。
        }
    }
支付寶
 1     public class StrategyContext2
 2     {
 3         private readonly IList<IStrategy> strategyList = new List<IStrategy>();
 4         /// <summary>
 5         /// 將所有策略都方法
 6         /// </summary>
 7         /// <param name="text"></param>
 8         public StrategyContext2(string text)
 9         {
10             strategyList.Add(new StrategyWeChat(text));
11             strategyList.Add(new StrategyAlipay(text));
12         }
13         /// <summary>
14         /// 調用具體的策略類實現掃碼解析方法
15         /// </summary>
16         public void AnalysisAction()
17         {
18             foreach (var item in strategyList)
19             {
20                 if (item.Analysisable)//判斷當前策略類是否可以處理
21                     item.AnalysisAction();
22             }
23         }
24     }
Context
1         private StrategyContext2 Context;
2         public void AnalysisAction(string text)
3         {
4             Context = new StrategyContext2(text);
5             Context.AnalysisAction();//自動實現解析,不用關心使用哪種策略
6         }

這樣我們就想決定權放到了具體策略類本身中。消滅了If else。如果再添加聯通掃碼策略的時候,只需要添加聯通的具體掃描策略,然后在context構造函數中把他加入到策略集合中。

但是這樣我們還是修改了context代碼。如果繼續想不修改context代碼呢?

繼續升級

我們可以使用反射,將所有策略實現類都反射出來,添加到策略集合中。那么我們的context類可以這樣寫:

    public class StrategyContext3
    {
        private readonly IList<IStrategy> strategyList = new List<IStrategy>();
        /// <summary>
        /// 將所有策略都方法
        /// </summary>
        /// <param name="text"></param>
        public StrategyContext3(string text)
        {
            //查詢程序集
            Assembly assembly = Assembly.GetExecutingAssembly();
            //找出繼承掃描策略接口的類
            IEnumerable<Type> types = assembly.GetTypes().Where(c => c.GetInterface("IStrategy") != null);
            foreach (var t in types)
            {
                object[] parameters = new object[1];
                parameters[0] = text;
                //創建類的實例
                strategyList.Add((IStrategy)Activator.CreateInstance(t, parameters));
            }
        }
        /// <summary>
        /// 調用具體的策略類實現掃碼解析方法
        /// </summary>
        public void AnalysisAction()
        {
            foreach (var item in strategyList)
            {
                if (item.Analysisable)//判斷當前策略類是否可以處理
                    item.AnalysisAction();
            }
        }
    }
context

應用場景

1、 多個類只區別在表現行為不同,可以使用Strategy模式,在運行時動態選擇具體要執行的行為。
2、 需要在不同情況下使用不同的策略(算法),或者策略還可能在未來用其它方式來實現。
3、 對客戶隱藏具體策略(算法)的實現細節,彼此完全獨立。

總結

  優點:策略模式是對算法的封裝,它把算法的責任和算法本身分割開,委派給不同的對象管理。

  缺點:客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用於客戶端知道所有的算法或行為的情況。 這也就是我們所說的if else並沒有真正的被消滅。

  改進:我們可以將決定使用哪種策略的權利放到策略類本身中,讓策略自己決定到底是不是用我自己的方法。從而實現消滅if else。

     可以通過反射,反射出所有的策略。這樣比較符合開閉原則。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM