手撸golang 结构型设计模式 门面模式
缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之
门面模式
门面模式(Facade Pattern)又叫作外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构型设计模式。
_
场景
- 某在线商城, 推出了积分兑换礼品的功能
兑换礼品有几个步骤, 涉及到若干子系统:
- 积分系统, 检查用户积分是否足够
- 库存系统, 检查礼品是否有库存
- 物流系统, 安排礼品发货并生成发货订单
- 为简化业务层接口, 以门面模式设计统一的积分兑换API接口 - IGiftExchangeService
设计
- GiftInfo: 礼品信息实体. 礼品也是一种库存物品.
- GiftExchangeRequest: 积分兑换礼品申请
- IGiftExchangeService: 积分兑换礼品服务, 该服务是一个Facade, 内部调用了多个子系统的服务
- IPointsService: 用户积分管理服务的接口
- IInventoryService: 库存管理服务的接口
- IShippingService: 物流下单服务的接口
- tMockGiftExchangeService: 积分兑换礼品服务的实现类
- tMockPointsService: 用户积分管理服务的实现类
- tMockInventoryService: 库存管理服务的实现类
- tMockShippingService: 物流下单服务的实现类
单元测试
facade_pattern_test.go
package structural_patterns
import (
"learning/gooop/structural_patterns/facade"
"testing"
"time"
)
func Test_FacadePattern(t *testing.T) {
iUserID := 1
iGiftID := 2
// 预先存入1000积分
e := facade.MockPointsService.SaveUserPoints(iUserID, 1000)
if e != nil {
t.Error(e)
return
}
// 预先存入1个库存
e = facade.MockInventoryService.SaveStock(iGiftID, 1)
if e != nil {
t.Error(e)
return
}
request := &facade.GiftExchangeRequest{
ID: 1,
UserID: iUserID,
GiftID: iGiftID,
CreateTime: time.Now().Unix(),
}
e, sOrderNo := facade.MockGiftExchangeService.Exchange(request)
if e != nil {
t.Log(e)
}
t.Logf("shipping order no = %v", sOrderNo)
}
测试输出
$ go test -v facade_pattern_test.go
=== RUN Test_FacadePattern
facade_pattern_test.go:36: shipping order no = shipping-order-666
--- PASS: Test_FacadePattern (0.00s)
PASS
ok command-line-arguments 0.002s
GiftInfo.go
礼品信息实体
package facade
type GiftInfo struct {
ID int
Name string
Points int
}
func NewGiftInfo(id int, name string, points int) *GiftInfo {
return &GiftInfo{
id, name, points,
}
}
GiftExchangeRequest.go
积分兑换礼品请求
package facade
type GiftExchangeRequest struct {
ID int
UserID int
GiftID int
CreateTime int64
}
IGiftExchangeService.go
积分兑换礼品的接口, 该接口是为方便客户端调用的Facade接口
package facade
// 礼品兑换服务
type IGiftExchangeService interface {
// 兑换礼品, 并返回物流单号
Exchange(request *GiftExchangeRequest) (error, string)
}
IPointsService.go
模拟用户积分管理服务的接口
package facade
// 用户积分服务
type IPointsService interface {
GetUserPoints(uid int) (error, int)
SaveUserPoints(uid int, points int) error
}
IInventoryService.go
模拟库存管理服务的接口
package facade
// 库存服务
type IInventoryService interface {
GetGift(goodsID int) *GiftInfo
GetStock(goodsID int) (error, int)
SaveStock(goodsID int, num int) error
}
IShippingService.go
模拟物流下单服务的接口
package facade
// 物流下单服务
type IShippingService interface {
CreateShippingOrder(uid int, goodsID int) (error, string)
}
tMockGiftExchangeService.go
实现积分兑换礼品服务. 内部封装了积分服务, 库存服务和物流下单服务的调用.
package facade
import "errors"
type tMockGiftExchangeService struct {
}
func newMockGiftExchangeService() IGiftExchangeService {
return &tMockGiftExchangeService{}
}
var MockGiftExchangeService = newMockGiftExchangeService()
// 模拟环境下未考虑事务提交和回滚
func (me *tMockGiftExchangeService) Exchange(request *GiftExchangeRequest) (error, string) {
gift := MockInventoryService.GetGift(request.GiftID)
if gift == nil {
return errors.New("gift not found"), ""
}
e, points := MockPointsService.GetUserPoints(request.UserID)
if e != nil {
return e, ""
}
if points < gift.Points {
return errors.New("insufficient user points"), ""
}
e, stock := MockInventoryService.GetStock(gift.ID)
if e != nil {
return e, ""
}
if stock <= 0 {
return errors.New("insufficient gift stock"), ""
}
e = MockInventoryService.SaveStock(gift.ID, stock-1)
if e != nil {
return e, ""
}
e = MockPointsService.SaveUserPoints(request.UserID, points - gift.Points)
if e != nil {
return e, ""
}
e,orderNo := MockShippingService.CreateShippingOrder(request.UserID, gift.ID)
if e != nil {
return e, ""
}
return nil, orderNo
}
tMockPointsService.go
模拟实现用户积分管理服务
package facade
import "errors"
var MockPointsService = newMockPointsService()
type tMockPointsService struct {
mUserPoints map[int]int
}
func newMockPointsService() IPointsService {
return &tMockPointsService{
make(map[int]int, 16),
}
}
func (me *tMockPointsService) GetUserPoints(uid int) (error, int) {
n,ok := me.mUserPoints[uid]
if ok {
return nil, n
} else {
return errors.New("user not found"), 0
}
}
func (me *tMockPointsService) SaveUserPoints(uid int, points int) error {
me.mUserPoints[uid] = points
return nil
}
tMockInventoryService.go
模拟实现库存管理服务
package facade
var MockInventoryService = newMockInventoryService()
type tMockInventoryService struct {
mGoodsStock map[int]int
}
func newMockInventoryService() IInventoryService {
return &tMockInventoryService{
make(map[int]int, 16),
}
}
func (me *tMockInventoryService) GetGift(id int) *GiftInfo {
return NewGiftInfo(id, "mock gift", 100)
}
func (me *tMockInventoryService) GetStock(goodsID int) (error, int) {
n,ok := me.mGoodsStock[goodsID]
if ok {
return nil, n
} else {
return nil, 0
}
}
func (me *tMockInventoryService) SaveStock(goodsID int, num int) error {
me.mGoodsStock[goodsID] = num
return nil
}
tMockShippingService.go
模拟实现物流下单服务
package facade
var MockShippingService = newMockShippingService()
type tMockShippingService struct {
}
func newMockShippingService() IShippingService {
return &tMockShippingService{}
}
func (me *tMockShippingService) CreateShippingOrder(uid int, goodsID int) (error, string) {
return nil, "shipping-order-666"
}
门面模式小结
门面模式的优点
(1)简化了调用过程,不用深入了解子系统,以防给子系统带来风险。
(2)减少系统依赖,松散耦合。
(3)更好地划分访问层次,提高了安全性。
(4)遵循迪米特法则
门面模式的缺点
(1)当增加子系统和扩展子系统行为时,可能容易带来未知风险。
(2)不符合开闭原则。
(3)某些情况下,可能违背单一职责原则。
(end)