6

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

 3 years ago
source link: https://studygolang.com/articles/33096
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK