Golang策略模式的思考

3,141 阅读4分钟

本文原创掘金: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实现的策略模式,优点是策略不需要于依赖于对象而存在,缺点是扩展没有传统方式方便。 水平有限,难免有错,欢迎拍砖。