缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

门面模式

门面模式(Facade Pattern)又叫作外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构型设计模式。
_

场景

  • 某在线商城, 推出了积分兑换礼品的功能
  • 兑换礼品有几个步骤, 涉及到若干子系统:
    • 积分系统, 检查用户积分是否足够
    • 库存系统, 检查礼品是否有库存
    • 物流系统, 安排礼品发货并生成发货订单
  • 为简化业务层接口, 以门面模式设计统一的积分兑换API接口 - IGiftExchangeService

设计

  • GiftInfo: 礼品信息实体. 礼品也是一种库存物品.
  • GiftExchangeRequest: 积分兑换礼品申请
  • IGiftExchangeService: 积分兑换礼品服务, 该服务是一个Facade, 内部调用了多个子系统的服务
  • IPointsService: 用户积分管理服务的接口
  • IInventoryService: 库存管理服务的接口
  • IShippingService: 物流下单服务的接口
  • tMockGiftExchangeService: 积分兑换礼品服务的实现类
  • tMockPointsService: 用户积分管理服务的实现类
  • tMockInventoryService: 库存管理服务的实现类
  • tMockShippingService: 物流下单服务的实现类

单元测试

facade_pattern_test.go

  1. package structural_patterns
  2. import (
  3. "learning/gooop/structural_patterns/facade"
  4. "testing"
  5. "time"
  6. )
  7. func Test_FacadePattern(t *testing.T) {
  8. iUserID := 1
  9. iGiftID := 2
  10. // 预先存入1000积分
  11. e := facade.MockPointsService.SaveUserPoints(iUserID, 1000)
  12. if e != nil {
  13. t.Error(e)
  14. return
  15. }
  16. // 预先存入1个库存
  17. e = facade.MockInventoryService.SaveStock(iGiftID, 1)
  18. if e != nil {
  19. t.Error(e)
  20. return
  21. }
  22. request := &facade.GiftExchangeRequest{
  23. ID: 1,
  24. UserID: iUserID,
  25. GiftID: iGiftID,
  26. CreateTime: time.Now().Unix(),
  27. }
  28. e, sOrderNo := facade.MockGiftExchangeService.Exchange(request)
  29. if e != nil {
  30. t.Log(e)
  31. }
  32. t.Logf("shipping order no = %v", sOrderNo)
  33. }

测试输出

  1. $ go test -v facade_pattern_test.go
  2. === RUN Test_FacadePattern
  3. facade_pattern_test.go:36: shipping order no = shipping-order-666
  4. --- PASS: Test_FacadePattern (0.00s)
  5. PASS
  6. ok command-line-arguments 0.002s

GiftInfo.go

礼品信息实体

  1. package facade
  2. type GiftInfo struct {
  3. ID int
  4. Name string
  5. Points int
  6. }
  7. func NewGiftInfo(id int, name string, points int) *GiftInfo {
  8. return &GiftInfo{
  9. id, name, points,
  10. }
  11. }

GiftExchangeRequest.go

积分兑换礼品请求

  1. package facade
  2. type GiftExchangeRequest struct {
  3. ID int
  4. UserID int
  5. GiftID int
  6. CreateTime int64
  7. }

IGiftExchangeService.go

积分兑换礼品的接口, 该接口是为方便客户端调用的Facade接口

  1. package facade
  2. // 礼品兑换服务
  3. type IGiftExchangeService interface {
  4. // 兑换礼品, 并返回物流单号
  5. Exchange(request *GiftExchangeRequest) (error, string)
  6. }

IPointsService.go

模拟用户积分管理服务的接口

  1. package facade
  2. // 用户积分服务
  3. type IPointsService interface {
  4. GetUserPoints(uid int) (error, int)
  5. SaveUserPoints(uid int, points int) error
  6. }

IInventoryService.go

模拟库存管理服务的接口

  1. package facade
  2. // 库存服务
  3. type IInventoryService interface {
  4. GetGift(goodsID int) *GiftInfo
  5. GetStock(goodsID int) (error, int)
  6. SaveStock(goodsID int, num int) error
  7. }

IShippingService.go

模拟物流下单服务的接口

  1. package facade
  2. // 物流下单服务
  3. type IShippingService interface {
  4. CreateShippingOrder(uid int, goodsID int) (error, string)
  5. }

tMockGiftExchangeService.go

实现积分兑换礼品服务. 内部封装了积分服务, 库存服务和物流下单服务的调用.

  1. package facade
  2. import "errors"
  3. type tMockGiftExchangeService struct {
  4. }
  5. func newMockGiftExchangeService() IGiftExchangeService {
  6. return &tMockGiftExchangeService{}
  7. }
  8. var MockGiftExchangeService = newMockGiftExchangeService()
  9. // 模拟环境下未考虑事务提交和回滚
  10. func (me *tMockGiftExchangeService) Exchange(request *GiftExchangeRequest) (error, string) {
  11. gift := MockInventoryService.GetGift(request.GiftID)
  12. if gift == nil {
  13. return errors.New("gift not found"), ""
  14. }
  15. e, points := MockPointsService.GetUserPoints(request.UserID)
  16. if e != nil {
  17. return e, ""
  18. }
  19. if points < gift.Points {
  20. return errors.New("insufficient user points"), ""
  21. }
  22. e, stock := MockInventoryService.GetStock(gift.ID)
  23. if e != nil {
  24. return e, ""
  25. }
  26. if stock <= 0 {
  27. return errors.New("insufficient gift stock"), ""
  28. }
  29. e = MockInventoryService.SaveStock(gift.ID, stock-1)
  30. if e != nil {
  31. return e, ""
  32. }
  33. e = MockPointsService.SaveUserPoints(request.UserID, points - gift.Points)
  34. if e != nil {
  35. return e, ""
  36. }
  37. e,orderNo := MockShippingService.CreateShippingOrder(request.UserID, gift.ID)
  38. if e != nil {
  39. return e, ""
  40. }
  41. return nil, orderNo
  42. }

tMockPointsService.go

模拟实现用户积分管理服务

  1. package facade
  2. import "errors"
  3. var MockPointsService = newMockPointsService()
  4. type tMockPointsService struct {
  5. mUserPoints map[int]int
  6. }
  7. func newMockPointsService() IPointsService {
  8. return &tMockPointsService{
  9. make(map[int]int, 16),
  10. }
  11. }
  12. func (me *tMockPointsService) GetUserPoints(uid int) (error, int) {
  13. n,ok := me.mUserPoints[uid]
  14. if ok {
  15. return nil, n
  16. } else {
  17. return errors.New("user not found"), 0
  18. }
  19. }
  20. func (me *tMockPointsService) SaveUserPoints(uid int, points int) error {
  21. me.mUserPoints[uid] = points
  22. return nil
  23. }

tMockInventoryService.go

模拟实现库存管理服务

  1. package facade
  2. var MockInventoryService = newMockInventoryService()
  3. type tMockInventoryService struct {
  4. mGoodsStock map[int]int
  5. }
  6. func newMockInventoryService() IInventoryService {
  7. return &tMockInventoryService{
  8. make(map[int]int, 16),
  9. }
  10. }
  11. func (me *tMockInventoryService) GetGift(id int) *GiftInfo {
  12. return NewGiftInfo(id, "mock gift", 100)
  13. }
  14. func (me *tMockInventoryService) GetStock(goodsID int) (error, int) {
  15. n,ok := me.mGoodsStock[goodsID]
  16. if ok {
  17. return nil, n
  18. } else {
  19. return nil, 0
  20. }
  21. }
  22. func (me *tMockInventoryService) SaveStock(goodsID int, num int) error {
  23. me.mGoodsStock[goodsID] = num
  24. return nil
  25. }

tMockShippingService.go

模拟实现物流下单服务

  1. package facade
  2. var MockShippingService = newMockShippingService()
  3. type tMockShippingService struct {
  4. }
  5. func newMockShippingService() IShippingService {
  6. return &tMockShippingService{}
  7. }
  8. func (me *tMockShippingService) CreateShippingOrder(uid int, goodsID int) (error, string) {
  9. return nil, "shipping-order-666"
  10. }

门面模式小结

门面模式的优点
(1)简化了调用过程,不用深入了解子系统,以防给子系统带来风险。
(2)减少系统依赖,松散耦合。
(3)更好地划分访问层次,提高了安全性。
(4)遵循迪米特法则
门面模式的缺点
(1)当增加子系统和扩展子系统行为时,可能容易带来未知风险。
(2)不符合开闭原则。
(3)某些情况下,可能违背单一职责原则。

(end)