Golang策略模式的思考

liangxingwei發表於2017-12-14

本文原創掘金:L_Sivan,原始碼demo在Github:LSivan-go_codes

策略模式是設計模式中的行為型模式,其核心是將部分的演算法實現以及排程分開來,從而實現向擴充套件開放,向修改關閉的物件導向的做法。之前寫過一篇,設計模式(八)——策略模式,這兩天想用下golang來實現的時候,發現之前寫的比較片面,沒進行更深入的思考,今天用golang實現的同時,來往深處鑽一下。

傳統策略模式

策略模式的根本是為了解決某一個場景可以出現的多個類似的演算法場景,最經典的,比如

if condition :
   // dosomething
else if condition:
  // doOtherThing
else:
 // doTheOtherThing
複製程式碼

然後具體的做法就是將這些分支的裡面的實現封裝一次,共同繼承一個基類或者實現一個介面,比如

public interface Strategy {
  void doSomething();
}

public class Strategy1 implements Strategy{
  void doSomething(){
    //...
  }
}
public class Strategy2 implements Strategy{
  void doSomething(){
    //...
  }
}
複製程式碼

接著在業務類組合一個基類物件並提供一個構造方法或set方法,比如

public Strategy strategy;
public void setStrategy(Strategy strategy){}
複製程式碼

最後在業務那裡就可以優化

public void service(){
  this.setsetStrategy(new Strategy1());
  this.strategy.doSomething();
}
複製程式碼

這樣做,就可以將分支的增長變為類的擴充套件,而且當策略之間有共同的一些操作,還可以用繼承的方法將這些操作封裝到父類中實現,這樣可以最大限度的避免程式碼的冗餘。(上面程式碼均以java的角度進行的分析) 然而策略模式的缺點也很明顯,第一,策略的膨脹會導致物件的膨脹,第二,策略的選擇還是需要人為了解策略實現後進行選擇,甚至還是不可避免地需要使用多個if else的巢狀來選擇策略 策略的選擇或許是通過下面的方法

if condition :
   this.setStrategy(new Strategy1());
else :
   this.setStrategy(new Strategy2());
strategy.doSomething();
複製程式碼

這種方法其實治標不治本,策略的選擇還是通過一個又臭又長的if else或者switch進行選擇,怎麼優化呢?好辦,預先將策略類儲存在一個map中,具體的開發時候,通過一個key來獲取這個策略的例項(這個key可以是前後端某個介面的選擇性引數,比如status:0,1,2)。 改進版

Map<String,Strategy> strategyMap = new HashMap<String,Strategy>();
strategyMap.put("Strategy1",new Strategy1());
strategyMap.put("Strategy2",new Strategy2());
strategyMap.get(condition).do(); 
複製程式碼

這樣的話,策略的選擇就通過變成了一個key的選擇,不需要每次呼叫都是例項化策略例項,甚至可以做到通過文件規範來制約。(介面文件來約束,比如付錢的介面,需要用支付寶還是微信支付,肯定有一個引數來選擇的,然後策略的選擇就通過這個引數來作為key)

於是,基於這種改進,golang的策略模式出來。

golang實現的”策略模式“

再重申一遍策略模式的精髓是封裝一組演算法實現以供使用時的排程,golang裡面有一個很重要的語法糖就是func()——方法變數,也因為,golang實現類似策略模式的做法,不需要依賴於物件而進行,比如

package main
import (
	"fmt"
)
var Strategy map[string]func(v ...interface{})
func init(){
    Strategy := make(map[string]func(v ...interface{}))
	Strategy["update"] = func(v ...interface{}){
		fmt.Println("update table set ? = ?")
	}
	Strategy["insert"] = func(v ...interface{}){
		fmt.Println("insert into table values(?,?,?,?)")
	}
	Strategy["delete"] = func(v ...interface{}){
		fmt.Println("delete from table where ?=?")
	}
	Strategy["select"] = func(v ...interface{}){
		fmt.Println("select ? from table where ?=?")
	}
}
func main(){
	Strategy["insert"]()
}
複製程式碼

golang這個func()方法變數的語法確實好用。

看回來,策略模式的核心是封裝一組演算法實現特別是相似的演算法實現,所以這個封裝,就封裝到方法變數中就好了,具體的呼叫過程,更是可以通過map來進行KV的約束,用文件規範好的欄位作為key,用具體的fun()作為value,這樣無論是演算法的封裝還是排程都從業務場景中解耦了,真的覺得很棒。當然,缺點就是如果需要擴充套件策略,就要到增加一個Entry<K,V>,沒有傳統的實現方式中直接擴充套件一個實現了策略介面的物件那麼方便,這兩個還得看具體的專案取捨,一句老話,沒有好壞,只有合適不合適。

總結

傳統的策略模式通過將某個演算法實現封裝到物件的方法中,在業務場景例項化具體的策略物件從而實現策略的選擇,而使用map來用key-value的方法改進策略的選擇後,更是大大改進了策略的選擇過程。優點是擴充套件方便,缺點是物件膨脹。 golang使用func()語法糖+map實現的策略模式,優點是策略不需要於依賴於物件而存在,缺點是擴充套件沒有傳統方式方便。 水平有限,難免有錯,歡迎拍磚。

相關文章