门面模式也叫外观模式,英文为 Facade Design Pattern。门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。 门面模式的思想更常用在架构设计上,在编写代码层面大家很少提门面模式,但却一直在默默的使用。
门面模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
UML:
分析
门面模式没有太多好分析的,思想比较简单。我方系统中包含多个子系统,完成一项任务需要多个子系统通力合作。
我们可以选择将子系统所有接口暴露给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)
}
总结
门面模式是程序员最常用的一种设计模式了,在架构设计中往往自然而然的就会用到。很好的满足了接口隔离原则和迪米特法则。