门面模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-47RVon20-1660307892231)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812202403501.png)]

门面模式也叫外观模式,英文为 Facade Design Pattern。门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。 门面模式的思想更常用在架构设计上,在编写代码层面大家很少提门面模式,但却一直在默默的使用。

门面模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

UML

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9MlL7WHl-1660307892233)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220812202626620.png)]

分析

门面模式没有太多好分析的,思想比较简单。我方系统中包含多个子系统,完成一项任务需要多个子系统通力合作。

我们可以选择将子系统所有接口暴露给Client,让Client自行调用。但这会导致一些问题,一是后期沟通成本会很高,加入完成一个功能需要调用多个接口,Client联调时出问题率会飙升,系统提供者需要不断答疑。二是如果有多个Client,相同代码Client需要重复开发,而且后期代码有变更,各方都会很烦费力。三是影响响应时间和性能,多个接口往返,白白增加了很多通信时间和请求量。

另一种方式是,对于指定功能,系统端做好封装,只提供一个接口。好处有很多,沟通成本低、Client不需要重复开发、功能更改影响范围小、提高响应时间和性能。一般这些接口会有对应的OpenAPI,实现了功能对外开放的效果。

使用场景

对于使用场景,简单的举一个例子。

电商系统一般包含商品、库存、营销、商家、交易、支付、售后、履约、物流、仓储等子系统。拿商品详情页来说,商详页接口一般会涉及商品、库存、营销、商家等系统。电商系统的客户端有PC、Mobile、Android、IOS等,如果让这些客户端调用接口拼凑出商详页的数据,感觉客户端的同学能拿着大砍刀和服务端同学谈心。为了避免这种情况,一般商品组同学会提供商详页接口,该接口获取商详页的所有信息,返回给客户端。

当然,如果流量特别大,需要优化接口性能,可以根据具体情况将接口做拆分,客户端需要请求多个接口,但即使这样,相关的接口也是封装好的。如果真实场景中遇到这种拆分的情况,那恭喜你,说明公司在发展,流量在增加,能够推动大家更快的成长。

代码实现

package main

import "fmt"

type ProductSystem struct {
}

func (p *ProductSystem) GetProductInfo() {
   fmt.Println("获取到商品信息")
}

type StockSystem struct {
}

func (s *StockSystem) GetStockInfo() {
   fmt.Println("获取到库存信息")
}

type PromotionSystem struct {
}

func (p *PromotionSystem) GetPromotionInfo() {
   fmt.Println("获取营销信息")
}

func ProductDetail() {
   product := &ProductSystem{}
   stock := &StockSystem{}
   promotion := &PromotionSystem{}
   product.GetProductInfo()
   stock.GetStockInfo()
   promotion.GetPromotionInfo()
   fmt.Println("整理完成商品详情页所有数据")
}
func main() {
   ProductDetail()
}
➜ myproject go run main.go

获取到商品信息

获取到库存信息

获取营销信息

整理完成商品详情页所有数据

实例

假设现在我有一个网站,以前有登录和注册的流程,登录的时候调用用户的查询接口,注册时调用用户的创建接口。为了简化用户的使用流程,我们现在提供直接验证码登录/注册的功能,如果该手机号已注册那么我们就走登录流程,如果该手机号未注册,那么我们就创建一个新的用户。

代码

package facade

// IUser 用户接口
type IUser interface {
	Login(phone int, code int) (*User, error)
	Register(phone int, code int) (*User, error)
}

// IUserFacade 门面模式
type IUserFacade interface {
	LoginOrRegister(phone int, code int) error
}

// User 用户
type User struct {
	Name string
}

// UserService UserService
type UserService struct {}

// Login 登录
func (u UserService) Login(phone int, code int) (*User, error) {
	// 校验操作 ...
	return &User{Name: "test login"}, nil
}

// Register 注册
func (u UserService) Register(phone int, code int) (*User, error) {
	// 校验操作 ...
	// 创建用户
	return &User{Name: "test register"}, nil
}

// LoginOrRegister 登录或注册
func (u UserService)LoginOrRegister(phone int, code int) (*User, error) {
	user, err := u.Login(phone, code)
	if err != nil {
		return nil, err
	}

	if user != nil {
		return user, nil
	}

	return u.Register(phone, code)
}

单元测试

package facade

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestUserService_Login(t *testing.T) {
	service := UserService{}
	user, err := service.Login(13001010101, 1234)
	assert.NoError(t, err)
	assert.Equal(t, &User{Name: "test login"}, user)
}

func TestUserService_LoginOrRegister(t *testing.T) {
	service := UserService{}
	user, err := service.LoginOrRegister(13001010101, 1234)
	assert.NoError(t, err)
	assert.Equal(t, &User{Name: "test login"}, user)
}

func TestUserService_Register(t *testing.T) {
	service := UserService{}
	user, err := service.Register(13001010101, 1234)
	assert.NoError(t, err)
	assert.Equal(t, &User{Name: "test register"}, user)
}

总结

门面模式是程序员最常用的一种设计模式了,在架构设计中往往自然而然的就会用到。很好的满足了接口隔离原则和迪米特法则。