《设计模式:可复用面向对象软件的基础》
策略允许算法独立于使用它的客户端而变化。
算法可以互相替换,不影响客户端
- 一个类定义多种行为,并且这些行为在这个类操作中以多个条件形式出现。将相关的条件移入它们各自的Strategy类中以替代这些条件语句。
- 许多相关类仅仅行为有异,需要用多个行为配置一个类的方法
- 需要使用一个算法的不同变体
Strategy策略:定义所有支持的算法的公共接口。
ConcreteStategy具体策略,以Strategy接口实现某具体算法
Contest 上下文:
用一个ConcreteStrategy对象来配置
维护一个对Strategy对象的引用
可定义一个接口让Stategy访问它的数据
客户创建并传递一个具体的策略A给到Context,Context将其转发给Strategy,这样就仅有客户和Context交互
需求:
假设现在就要做一个营销,需要用户参与一个活动,然后完成一系列的任务,最后可以得到一些奖励作为回报。活动的奖励包含美团外卖、酒旅和美食等多种品类券,现在需要你帮忙设计一套奖励发放方案
完整案例:
https://tech.meituan.com/2022/03/10/interesting-talk-about-design-patterns.html
例子概括:
美团的奖励的例子,从一开始的多个if到后面彻底删除条件判断
方案1:通过奖励类型进行判断调用什么函数,多个if条件进行判断,不符合开闭原则、迪米特法则
方案2:使用策略模式,抽象一层奖励策略接口,具体的奖励类型作为具体的策略实现这一奖励策略接口,增加一个环境类(内免除不了分支判断),供给奖励服务调用。
降低了耦合度
方案3:单例模式、优化环境类
环境类新增Map注册表,注册所有具体策略,传入奖励类型,就可以通过map匹配到具体策略。
使用饿汉式单例模式
package main
import (
"fmt"
)
// Strategy 策略接口
type Strategy interface {
issue()
}
// 策略上下文,用于管理策略的注册和获取--------------
type StrategyContext struct {
registerMap map[string]Strategy
}
// 注册策略
func (c *StrategyContext) RegisterStrategy(rewardType string, strategy Strategy) {
if c.registerMap == nil {
c.registerMap = make(map[string]Strategy)
}
c.registerMap[rewardType] = strategy
}
// 获取策略
func (c *StrategyContext) GetStrategy(rewardType string) Strategy {
return c.registerMap[rewardType]
}
//--------------
// 单例外卖策略 --------------
type WaimaiService struct{}
func (w *WaimaiService) Issue() {
fmt.Println("Waimai Reward")
}
type WaimaiStrategy struct {
waimaiService WaimaiService
}
var instanceWaimai *WaimaiStrategy
// 获取单例
func GetInstanceWaimai() *WaimaiStrategy {
if instanceWaimai == nil {
instanceWaimai = &WaimaiStrategy{waimaiService: WaimaiService{}}
}
return instanceWaimai
}
// 实现接口方法(go:一个类型只需要实现接口声明的所有方法即可被认定为实现了这个接口)
func (w *WaimaiStrategy) issue() {
w.waimaiService.Issue()
}
// 单例酒店策略-----------
type HotelStrategy struct {
hotelService HotelService
}
type HotelService struct{}
func (w *HotelService) Issue() {
fmt.Println("Hotel Reward")
}
var instanceHotel *HotelStrategy
// 获取单例
func GetInstanceHotel() *HotelStrategy {
if instanceHotel == nil {
instanceHotel = &HotelStrategy{hotelService: HotelService{}}
}
return instanceHotel
}
// 实现接口方法
func (h *HotelStrategy) issue() {
h.hotelService.Issue()
}
// 单例美食策略-----------
type FoodStrategy struct {
foodService FoodService
}
type FoodService struct{}
func (w *FoodService) Issue() {
fmt.Println("Food Reward")
}
var instanceFood *FoodStrategy
// 获取单例
func GetInstanceFood() *FoodStrategy {
if instanceFood == nil {
instanceFood = &FoodStrategy{foodService: FoodService{}}
}
return instanceFood
}
// 实现接口方法
func (f *FoodStrategy) issue() {
f.foodService.Issue()
}
// 注册所有策略
func main() {
// go只有init进行初始化,但是这样我觉得破坏了开闭原则,所以这个操作放在main
// 但是这样每次用之前都要注册一次,不实际,实际使用得在init统一注册
context := &StrategyContext{}
//外卖策略注册,调用
waimaiInstance := GetInstanceWaimai()
context.RegisterStrategy(
"waimai",
waimaiInstance,
)
context.GetStrategy("waimai").issue()
//酒店
hotelInstance := GetInstanceHotel()
context.RegisterStrategy(
"hotel",
hotelInstance,
)
context.GetStrategy("hotel").issue()
//美食
foodInstance := GetInstanceFood()
context.RegisterStrategy(
"food",
foodInstance,
)
context.GetStrategy("food").issue()
}
//Waimai Reward
//Hotel Reward
//Food Reward
不够满意,go的初始化操作只有init,整个包的初始化都要统一放在一个函数,没有像python的__init__这样一个类自己有一个自执行函数,这样修改go的init函数貌似也破坏了开闭原则,所以只想到放在main