策略模式
策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。
工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式跟两者类似,也能起到解耦的作用,不过,它解耦的是策略的定义、创建、使用这三部分。
举个例子
假设我们在保存文件的时候,由于政策或者其他的原因可能需要选择不同的存储方式,敏感数据我们需要加密存储,不敏感的数据我们可以直接明文保存。
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
}
详细版例子
// 策略模式:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)
// 解耦策略的定义、创建、使用
// 面向接口编程
package main
import (
"errors"
"fmt"
)
/** 1. 策略定义 */
type Strategy interface {
algorithmInterface()
}
// class ConcreteStrategyA
type ConcreteStrategyA struct{}
func NewConcreteStrategyA() ConcreteStrategyA {
return ConcreteStrategyA{}
}
func (c ConcreteStrategyA) algorithmInterface() {
// 具体的算法...
fmt.Println("algorithm A")
}
// class ConcreteStrategyB
type ConcreteStrategyB struct{}
func NewConcreteStrategyB() ConcreteStrategyB {
return ConcreteStrategyB{}
}
func (c ConcreteStrategyB) algorithmInterface() {
// 具体的算法...
fmt.Println("algorithm B")
}
/** 2. 策略创建 */
// 方式一:针对无状态策略类,不需要每次都实时创建
type StrategyFactory struct {
strategies map[string]Strategy
}
func NewStrategyFactory() StrategyFactory {
strategies := make(map[string]Strategy)
strategies["A"] = NewConcreteStrategyA()
strategies["B"] = NewConcreteStrategyB()
return StrategyFactory{strategies: strategies}
}
func (sf StrategyFactory) getStrategy(strategyType string) (Strategy, error) {
if strategyType == "" {
return nil, errors.New("type should not be empty")
}
// 查表法
strategy, ok := sf.strategies[strategyType]
if !ok {
return nil, errors.New("strategyType is invalid")
}
return strategy, nil
}
// 方式二:有状态策略类,每次都需要创建新的策略对象
type StrategyFactory2 struct{}
func NewStrategyFactoryV2() StrategyFactory2 {
return StrategyFactory2{}
}
func (sf StrategyFactory2) getStrategy(strategyType string) (Strategy, error) {
if strategyType == "" {
return nil, errors.New("type should not be empty")
}
if strategyType == "A" {
return NewConcreteStrategyA(), nil
}
if strategyType == "B" {
return NewConcreteStrategyB(), nil
}
return nil, errors.New("strategyType is invalid")
}
/** 3. 策略使用 */
func Demo() {
sf := NewStrategyFactory()
strategyType := getStrategyType()
// 获取策略,这一步通过修改 strategyType,可以灵活替换策略
strategy, err := sf.getStrategy(strategyType)
if err != nil {
panic("load strategy failed")
}
strategy.algorithmInterface()
}
// A、B 可能从配置或参数中读取
func getStrategyType() string {
return "A"
}
如果光从代码来看,策略模式和工厂模式有一些相似,只是多了策略使用这一部分,但要牢记一点,设计模式是和场景绑定的。策略模式面向的是算法类的定义、创建和使用,比如不同促销活动下的价格计算、不同场景下的排序算法选择等。