策略模式核心在于利用多态性,这是目前主流的面向对象语言都支持的功能。
策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
UML:
分析
单看策略模式,主要利用多态性。但策略模式往往不单独使用,它会和工厂模式配合使用。此时策略模式便可解耦策略的定义、创建、使用。
策略模式加工厂模式,能够达到去除if-else和switch的效果,主要靠工厂模式加持,借助于“查表法”找到指定策略进行使用。
应用场景
策略模式的应用场景还是蛮广泛的,处理同一类问题如果有多种算法,就可以使用策略模式。如根据不同活动计算优惠价格、根据商品不同类型计算税率等。
最近有一个实际业务场景可以使用策略模式,计算跨境商品税费。
跨境商品的税费和两方面有关,一是商品是否含税,二是商品类型,不同类型对应税率不一样,如常规商品和酒类商品税率不一样、税的计算方式也不一样。
所以只要知道商品是否含税、商品类型就能找到对应的计算方案。如果我们使用if-else来写,会不优雅,因为商品类型比较多,而且计算逻辑也相对复杂,所以我们可以利用查表法进行优化。
税费计算接口被交易侧调用,交易调用的时候会传商品是否含税,商品类型由项目组自行维护。那我们来看一下具体实现。
代码实现
package main
import "fmt"
const (
Common = "COMMON"
Win = "WIN"
)
/**
* @Description: 根据hscode获取商品类型
* @param hscode
* @return string
*/
func getProductType(hscode string) string {
if hscode == "11" {
return Common
} else {
return Win
}
}
/**
* @Description: 税费计算函数,金额都为分
* @param price
* @param qty
* @return taxPrice
*/
type TaxComputeFunc func(price int64, qty int64) (taxPrice int64)
/**
* @Description: 税费计算策略存储处
0为不含税 1为含税
*/
var TaxComputeFuncMap = map[int]map[string]TaxComputeFunc{
0: map[string]TaxComputeFunc{
Common: common,
Win: win,
},
1: map[string]TaxComputeFunc{
Common: common,
Win: win,
},
}
/**
* @Description: 计算普通商品税费
* @param price
* @param qty
* @return taxPrice
*/
func common(price int64, qty int64) (taxPrice int64) {
radio := 0.1
fmt.Println("计算普通商品税费")
return int64(float64(price*qty) * radio)
}
/**
* @Description: 计算酒类税费
* @param price
* @param qty
* @return taxPrice
*/
func win(price int64, qty int64) (taxPrice int64) {
radio := 0.2
fmt.Println("计算普酒类税费")
return int64(float64(price*qty) * radio)
}
/**
* @Description: 计算税费
* @param withTax
* @param productType
* @param price
* @param qty
*/
func ComputeTaxPrice(withTax int, productType string, price int64, qty int64) {
if taxFunc, ok := TaxComputeFuncMap[withTax][productType]; ok {
taxPrice := taxFunc(price, qty)
fmt.Println("税费为", taxPrice)
} else {
fmt.Println("输入有误,无法计算")
}
}
func main() {
//获取商品是否含税、商品价格、商品数量、商品类型
withTax := 0
var price, qty int64 = 10000, 3
productType := getProductType("11")
//计算税费
ComputeTaxPrice(withTax, productType, price, qty)
}
输出:
➜ myproject go run main.go
计算普通商品税费
税费为 3000
这么写有哪些好处呢?首先代码很精简,没有一堆判断;其次无论是增加策略还是修改策略,都不会影响主框架,即main\ComputeTaxPrice,只需在TaxComputeFuncMap添加新的策略即可,很好的做到了对扩展开放,虽然对TaxComputeFuncMap有一定更改,但变动不大,能够接受。
实例
来个 🌰,我们在保存文件的时候,由于政策或者其他的原因可能需要选择不同的存储方式,敏感数据我们需要加密存储,不敏感的数据我们可以直接明文保存。
代码
package strategy
import (
"fmt"
"io/ioutil"
"os"
)
// StorageStrategy 存储策略
type StorageStrategy interface {
Save(name string, data []byte) error
}
var strategys = map[string]StorageStrategy{
"file": &fileStorage{},
"encrypt_file": &encryptFileStorage{},
}
// NewStorageStrategy NewStorageStrategy
func NewStorageStrategy(t string) (StorageStrategy, error) {
s, ok := strategys[t]
if !ok {
return nil, fmt.Errorf("not found StorageStrategy: %s", t)
}
return s, nil
}
// FileStorage 保存到文件
type fileStorage struct{}
// Save Save
func (s *fileStorage) Save(name string, data []byte) error {
return ioutil.WriteFile(name, data, os.ModeAppend)
}
// encryptFileStorage 加密保存到文件
type encryptFileStorage struct{}
// Save Save
func (s *encryptFileStorage) Save(name string, data []byte) error {
// 加密
data, err := encrypt(data)
if err != nil {
return err
}
return ioutil.WriteFile(name, data, os.ModeAppend)
}
func encrypt(data []byte) ([]byte, error) {
// 这里实现加密算法
return data, nil
}
单元测试
package strategy
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_demo(t *testing.T) {
// 假设这里获取数据,以及数据是否敏感
data, sensitive := getData()
strategyType := "file"
if sensitive {
strategyType = "encrypt_file"
}
storage, err := NewStorageStrategy(strategyType)
assert.NoError(t, err)
assert.NoError(t, storage.Save("./test.txt", data))
}
// getData 获取数据的方法
// 返回数据,以及数据是否敏感
func getData() ([]byte, bool) {
return []byte("test data"), false
}
总结
策略模式很好的体现了开闭原则,也说明即使是很小的优化设计,也能给项目开发带来巨大的便利。当然,这种便利会在维护的时候得到充分体现,毕竟谁维护谁知道。