手撸golang 结构型设计模式 门面模式

手撸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)

有疑问加站长微信联系(非本文作者)