关于go:快收藏最全GO语言实现设计模式

导语| 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种通过实际验证的解决方案。即便你从未遇到过这些问题,理解模式依然十分有用,因为它能领导你如何应用面向对象的设计准则来解决各种问题,进步开发效率,升高开发成本;本文囊括了GO语言实现的经典设计模式示例,每个示例都精心设计,力求合乎模式构造,可作为日常编码参考,同时一些罕用的设计模式融入了开发实际经验总结,帮忙大家在平时工作中灵活运用。

责任链模式

(一)概念

责任链模式是一种行为设计模式, 容许你将申请沿着解决者链进行发送。收到申请后,每个解决者均可对申请进行解决,或将其传递给链上的下个解决者。

该模式容许多个对象来对申请进行解决,而无需让发送者类与具体接收者类相耦合。链可在运行时由遵循规范解决者接口的任意解决者动静生成。

个别意义上的责任链模式是说,申请在链上流转时任何一个满足条件的节点解决完申请后就会进行流转并返回,不过还能够依据不同的业务状况做一些改良:

  • 申请能够流经解决链的所有节点,不同节点会对申请做不同职责的解决;
  • 能够通过上下文参数保留申请对象及上游节点的处理结果,供上游节点依赖,并进一步解决;
  • 解决链可反对节点的异步解决,通过实现特定接口判断,是否须要异步解决;
  • 责任链对于申请解决节点能够设置进行标记位,不是异样,是一种满足业务流转的中断;
  • 责任链的拼接形式存在两种,一种是节点遍历,一个节点一个节点程序执行;另一种是节点嵌套,内层节点嵌入在外层节点执行逻辑中,相似递归,或者“回”行构造;
  • 责任链的节点嵌套拼接形式多被称为拦截器链或者过滤器链,更易于实现业务流程的切面,比方监控业务执行时长,日志输入,权限校验等;

(二)示例

本示例模仿实现机场登机过程,第一步办理登机牌,第二步如果有行李,就办理托运,第三步核实身份,第四步安全检查,第五步实现登机;其中行李托运是可选的,其余步骤必选,必选步骤有任何不满足就终止登机;旅客对象作为申请参数上下文,每个步骤会依据旅客对象状态判断是否解决或流转下一个节点;

(三)登机过程

package chainofresponsibility

import "fmt"

// BoardingProcessor 登机过程中,各节点对立解决接口
type BoardingProcessor interface {
  SetNextProcessor(processor BoardingProcessor)
  ProcessFor(passenger *Passenger)
}

// Passenger 旅客
type Passenger struct {
  name                  string // 姓名
  hasBoardingPass       bool   // 是否办理登机牌
  hasLuggage            bool   // 是否有行李须要托运
  isPassIdentityCheck   bool   // 是否通过身份校验
  isPassSecurityCheck   bool   // 是否通过安检
  isCompleteForBoarding bool   // 是否实现登机
}

// baseBoardingProcessor 登机流程处理器基类
type baseBoardingProcessor struct {
  // nextProcessor 下一个登机解决流程
  nextProcessor BoardingProcessor
}

// SetNextProcessor 基类中对立实现设置下一个处理器办法
func (b *baseBoardingProcessor) SetNextProcessor(processor BoardingProcessor) {
  b.nextProcessor = processor
}

// ProcessFor 基类中对立实现下一个处理器流转
func (b *baseBoardingProcessor) ProcessFor(passenger *Passenger) {
  if b.nextProcessor != nil {
    b.nextProcessor.ProcessFor(passenger)
  }
}

// boardingPassProcessor 办理登机牌处理器
type boardingPassProcessor struct {
  baseBoardingProcessor // 援用基类
}

func (b *boardingPassProcessor) ProcessFor(passenger *Passenger) {
  if !passenger.hasBoardingPass {
    fmt.Printf("为旅客%s办理登机牌;\n", passenger.name)
    passenger.hasBoardingPass = true
  }
  // 胜利办理登机牌后,进入下一个流程解决
  b.baseBoardingProcessor.ProcessFor(passenger)
}

// luggageCheckInProcessor 托运行李处理器
type luggageCheckInProcessor struct {
  baseBoardingProcessor
}

func (l *luggageCheckInProcessor) ProcessFor(passenger *Passenger) {
  if !passenger.hasBoardingPass {
    fmt.Printf("旅客%s未办理登机牌,不能托运行李;\n", passenger.name)
    return
  }
  if passenger.hasLuggage {
    fmt.Printf("为旅客%s办理行李托运;\n", passenger.name)
  }
  l.baseBoardingProcessor.ProcessFor(passenger)
}

// identityCheckProcessor 校验身份处理器
type identityCheckProcessor struct {
  baseBoardingProcessor
}

func (i *identityCheckProcessor) ProcessFor(passenger *Passenger) {
  if !passenger.hasBoardingPass {
    fmt.Printf("旅客%s未办理登机牌,不能办理身份校验;\n", passenger.name)
    return
  }
  if !passenger.isPassIdentityCheck {
    fmt.Printf("为旅客%s核实身份信息;\n", passenger.name)
    passenger.isPassIdentityCheck = true
  }
  i.baseBoardingProcessor.ProcessFor(passenger)
}

// securityCheckProcessor 安检处理器
type securityCheckProcessor struct {
  baseBoardingProcessor
}

func (s *securityCheckProcessor) ProcessFor(passenger *Passenger) {
  if !passenger.hasBoardingPass {
    fmt.Printf("旅客%s未办理登机牌,不能进行安检;\n", passenger.name)
    return
  }
  if !passenger.isPassSecurityCheck {
    fmt.Printf("为旅客%s进行安检;\n", passenger.name)
    passenger.isPassSecurityCheck = true
  }
  s.baseBoardingProcessor.ProcessFor(passenger)
}

// completeBoardingProcessor 实现登机处理器
type completeBoardingProcessor struct {
  baseBoardingProcessor
}

func (c *completeBoardingProcessor) ProcessFor(passenger *Passenger) {
  if !passenger.hasBoardingPass ||
    !passenger.isPassIdentityCheck ||
    !passenger.isPassSecurityCheck {
    fmt.Printf("旅客%s登机查看过程未实现,不能登机;\n", passenger.name)
    return
  }
  passenger.isCompleteForBoarding = true
  fmt.Printf("旅客%s胜利登机;\n", passenger.name)
}

(四)测试程序

package chainofresponsibility

import "testing"

func TestChainOfResponsibility(t *testing.T) {
  boardingProcessor := BuildBoardingProcessorChain()
  passenger := &Passenger{
    name:                  "李四",
    hasBoardingPass:       false,
    hasLuggage:            true,
    isPassIdentityCheck:   false,
    isPassSecurityCheck:   false,
    isCompleteForBoarding: false,
  }
  boardingProcessor.ProcessFor(passenger)
}

// BuildBoardingProcessorChain 构建登机流程解决链
func BuildBoardingProcessorChain() BoardingProcessor {
  completeBoardingNode := &completeBoardingProcessor{}

  securityCheckNode := &securityCheckProcessor{}
  securityCheckNode.SetNextProcessor(completeBoardingNode)

  identityCheckNode := &identityCheckProcessor{}
  identityCheckNode.SetNextProcessor(securityCheckNode)

  luggageCheckInNode := &luggageCheckInProcessor{}
  luggageCheckInNode.SetNextProcessor(identityCheckNode)

  boardingPassNode := &boardingPassProcessor{}
  boardingPassNode.SetNextProcessor(luggageCheckInNode)
  return boardingPassNode
}

(五)运行后果

=== RUN   TestChainOfResponsibility
为旅客李四办理登机牌;
为旅客李四办理行李托运;
为旅客李四核实身份信息;
为旅客李四进行安检;
旅客李四胜利登机;
--- PASS: TestChainOfResponsibility (0.00s)
PASS

命令模式

(一)概念

命令模式是一种行为设计模式,它可将申请转换为一个蕴含与申请相干的所有信息的独立对象。该转换让你能依据不同的申请将办法参数化、提早申请执行或将其放入队列中,且能实现可撤销操作。

办法参数化是指将每个申请参数传入具体命令的工厂办法(go语言没有构造函数)创立命令,同时具体命令会默认设置好承受对象,这样做的益处是不论申请参数个数及类型,还是承受对象有几个,都会被封装到具体命令对象的成员字段上,并通过对立的Execute接口办法进行调用,屏蔽各个申请的差别,便于命令扩大,多命令组装,回滚等;

(二)示例

管制电饭煲做饭是一个典型的命令模式的场景,电饭煲的控制面板会提供设置煮粥、蒸饭模式,及开始和进行按钮,电饭煲控制系统会依据模式的不同设置相应的火力,压强及工夫等参数;煮粥,蒸饭就相当于不同的命令,开始按钮就相当命令触发器,设置好做饭模式,点击开始按钮电饭煲就开始运行,同时还反对进行命令;

(三)电饭煲接收器

package command

import "fmt"

// ElectricCooker 电饭煲
type ElectricCooker struct {
  fire     string // 火力
  pressure string // 压力
}

// SetFire 设置火力
func (e *ElectricCooker) SetFire(fire string) {
  e.fire = fire
}

// SetPressure 设置压力
func (e *ElectricCooker) SetPressure(pressure string) {
  e.pressure = pressure
}

// Run 继续运行指定工夫
func (e *ElectricCooker) Run(duration string) string {
  return fmt.Sprintf("电饭煲设置火力为%s,压力为%s,继续运行%s;", e.fire, e.pressure, duration)
}

// Shutdown 进行
func (e *ElectricCooker) Shutdown() string {
  return "电饭煲进行运行。"
}

(四)电饭煲命令

package command

// CookCommand 做饭指令接口
type CookCommand interface {
  Execute() string // 指令执行办法
}

// steamRiceCommand 蒸饭指令
type steamRiceCommand struct {
  electricCooker *ElectricCooker // 电饭煲
}

func NewSteamRiceCommand(electricCooker *ElectricCooker) *steamRiceCommand {
  return &steamRiceCommand{
    electricCooker: electricCooker,
  }
}

func (s *steamRiceCommand) Execute() string {
  s.electricCooker.SetFire("中")
  s.electricCooker.SetPressure("失常")
  return "蒸饭:" + s.electricCooker.Run("30分钟")
}

// cookCongeeCommand 煮粥指令
type cookCongeeCommand struct {
  electricCooker *ElectricCooker
}

func NewCookCongeeCommand(electricCooker *ElectricCooker) *cookCongeeCommand {
  return &cookCongeeCommand{
    electricCooker: electricCooker,
  }
}

func (c *cookCongeeCommand) Execute() string {
  c.electricCooker.SetFire("大")
  c.electricCooker.SetPressure("强")
  return "煮粥:" + c.electricCooker.Run("45分钟")
}

// shutdownCommand 进行指令
type shutdownCommand struct {
  electricCooker *ElectricCooker
}

func NewShutdownCommand(electricCooker *ElectricCooker) *shutdownCommand {
  return &shutdownCommand{
    electricCooker: electricCooker,
  }
}

func (s *shutdownCommand) Execute() string {
  return s.electricCooker.Shutdown()
}

// ElectricCookerInvoker 电饭煲指令触发器
type ElectricCookerInvoker struct {
  cookCommand CookCommand
}

// SetCookCommand 设置指令
func (e *ElectricCookerInvoker) SetCookCommand(cookCommand CookCommand) {
  e.cookCommand = cookCommand
}

// ExecuteCookCommand 执行指令
func (e *ElectricCookerInvoker) ExecuteCookCommand() string {
  return e.cookCommand.Execute()
}

(五)测试程序

package command

import (
  "fmt"
  "testing"
)

func TestCommand(t *testing.T) {
  // 创立电饭煲,命令接受者
  electricCooker := new(ElectricCooker)
  // 创立电饭煲指令触发器
  electricCookerInvoker := new(ElectricCookerInvoker)

  // 蒸饭
  steamRiceCommand := NewSteamRiceCommand(electricCooker)
  electricCookerInvoker.SetCookCommand(steamRiceCommand)
  fmt.Println(electricCookerInvoker.ExecuteCookCommand())

  // 煮粥
  cookCongeeCommand := NewCookCongeeCommand(electricCooker)
  electricCookerInvoker.SetCookCommand(cookCongeeCommand)
  fmt.Println(electricCookerInvoker.ExecuteCookCommand())

  // 进行
  shutdownCommand := NewShutdownCommand(electricCooker)
  electricCookerInvoker.SetCookCommand(shutdownCommand)
  fmt.Println(electricCookerInvoker.ExecuteCookCommand())
}

(六)运行后果

=== RUN   TestCommand
蒸饭:电饭煲设置火力为中,压力为失常,继续运行30分钟;
煮粥:电饭煲设置火力为大,压力为强,继续运行45分钟;
电饭煲进行运行。
--- PASS: TestCommand (0.00s)
PASS

迭代器模式

============

(一)概念

迭代器模式是一种行为设计模式,让你能在不裸露汇合底层表现形式 (列表、 栈和树等)的状况下遍历汇合中所有的元素。

在迭代器的帮忙下, 客户端能够用一个迭代器接口以类似的形式遍历不同汇合中的元素。

这里须要留神的是有两个典型的迭代器接口须要分分明;一个是汇合类实现的能够创立迭代器的工厂办法接口个别命名为Iterable,蕴含的办法相似CreateIterator;另一个是迭代器自身的接口,命名为Iterator,有Next及hasMore两个次要办法;

(二)示例

一个班级类中包含一个老师和若干个学生,咱们要对班级所有成员进行遍历,班级中老师存储在独自的构造字段中,学生存储在另外一个slice字段中,通过迭代器,咱们实现对立遍历解决;

(三)班级成员

package iterator

import "fmt"

// Member 成员接口
type Member interface {
  Desc() string // 输入成员形容信息
}

// Teacher 老师
type Teacher struct {
  name    string // 名称
  subject string // 所教课程
}

// NewTeacher 依据姓名、课程创立老师对象
func NewTeacher(name, subject string) *Teacher {
  return &Teacher{
    name:    name,
    subject: subject,
  }
}

func (t *Teacher) Desc() string {
  return fmt.Sprintf("%s班主任老师负责教%s", t.name, t.subject)
}

// Student 学生
type Student struct {
  name     string // 姓名
  sumScore int    // 考试总分数
}

// NewStudent 创立学生对象
func NewStudent(name string, sumScore int) *Student {
  return &Student{
    name:     name,
    sumScore: sumScore,
  }
}

func (t *Student) Desc() string {
  return fmt.Sprintf("%s同学考试总分为%d", t.name, t.sumScore)
}

(四)班级成员迭代器

package iterator

// Iterator 迭代器接口
type Iterator interface {
  Next() Member  // 迭代下一个成员
  HasMore() bool // 是否还有
}

// memberIterator 班级成员迭代器实现
type memberIterator struct {
  class *Class // 需迭代的班级
  index int    // 迭代索引
}

func (m *memberIterator) Next() Member {
  // 迭代索引为-1时,返回老师成员,否则遍历学生slice
  if m.index == -1 {
    m.index++
    return m.class.teacher
  }
  student := m.class.students[m.index]
  m.index++
  return student
}

func (m *memberIterator) HasMore() bool {
  return m.index < len(m.class.students)
}

// Iterable 可迭代汇合接口,实现此接口返回迭代器
type Iterable interface {
  CreateIterator() Iterator
}

// Class 班级,包含老师和同学
type Class struct {
  name     string
  teacher  *Teacher
  students []*Student
}

// NewClass 依据班主任老师名称,授课创立班级
func NewClass(name, teacherName, teacherSubject string) *Class {
  return &Class{
    name:    name,
    teacher: NewTeacher(teacherName, teacherSubject),
  }
}

// CreateIterator 创立班级迭代器
func (c *Class) CreateIterator() Iterator {
  return &memberIterator{
    class: c,
    index: -1, // 迭代索引初始化为-1,从老师开始迭代
  }
}

func (c *Class) Name() string {
  return c.name
}

// AddStudent 班级增加同学
func (c *Class) AddStudent(students ...*Student) {
  c.students = append(c.students, students...)
}

(五)测试程序

package iterator

import (
  "fmt"
  "testing"
)

func TestIterator(t *testing.T) {
  class := NewClass("三年级一班", "王明", "数学课")
  class.AddStudent(NewStudent("张三", 389),
    NewStudent("李四", 378),
    NewStudent("王五", 347))

  fmt.Printf("%s成员如下:\n", class.Name())
  classIterator := class.CreateIterator()
  for classIterator.HasMore() {
    member := classIterator.Next()
    fmt.Println(member.Desc())
  }
}

(六)运行后果

=== RUN   TestIterator
三年级一班成员如下:
王明班主任老师负责教数学课
张三同学考试总分为389
李四同学考试总分为378
王五同学考试总分为347
--- PASS: TestIterator (0.00s)
PASS

中介者模式

(一)概念

中介者模式是一种行为设计模式,能让你缩小对象之间凌乱无序的依赖关系。该模式会限度对象之间的间接交互,迫使它们通过一个中介者对象进行单干,将网状依赖变为星状依赖。

中介者能使得程序更易于批改和扩大,而且能更不便地对独立的组件进行复用,因为它们不再依赖于很多其余的类。

中介者模式与观察者模式之间的区别是,中介者模式解决的是同类或者不同类的多个对象之间多对多的依赖关系,观察者模式解决的是多个对象与一个对象之间的多对一的依赖关系。

(二)示例

机场塔台调度零碎是一个体现中介者模式的典型示例,假如是一个小机场,每次只能同时容许一架飞机起降,每架凑近机场的飞机须要先与塔台沟通是否能够起飞,如果没有闲暇的跑道,须要在天空回旋期待,如果有飞机离港,期待的飞机会收到塔台的告诉,按先后顺序起飞;这种形式,免去多架飞机同时达到机场须要互相沟通起飞程序的复杂性,缩小多个飞机间的依赖关系,简化业务逻辑,从而升高零碎出问题的危险。

(三)飞机对象

package mediator

import "fmt"

// Aircraft 飞机接口
type Aircraft interface {
  ApproachAirport() // 到达机场空域
  DepartAirport()   // 飞离机场
}

// airliner 客机
type airliner struct {
  name            string          // 客机型号
  airportMediator AirportMediator // 机场调度
}

// NewAirliner 依据指定型号及机场调度创立客机
func NewAirliner(name string, mediator AirportMediator) *airliner {
  return &airliner{
    name:            name,
    airportMediator: mediator,
  }
}

func (a *airliner) ApproachAirport() {
  if !a.airportMediator.CanLandAirport(a) { // 申请塔台是否能够起飞
    fmt.Printf("机场忙碌,客机%s持续期待起飞;\n", a.name)
    return
  }
  fmt.Printf("客机%s胜利滑翔起飞机场;\n", a.name)
}

func (a *airliner) DepartAirport() {
  fmt.Printf("客机%s胜利滑翔腾飞,来到机场;\n", a.name)
  a.airportMediator.NotifyWaitingAircraft() // 告诉期待的其余飞机
}

// helicopter 直升机
type helicopter struct {
  name            string
  airportMediator AirportMediator
}

// NewHelicopter 依据指定型号及机场调度创立直升机
func NewHelicopter(name string, mediator AirportMediator) *helicopter {
  return &helicopter{
    name:            name,
    airportMediator: mediator,
  }
}

func (h *helicopter) ApproachAirport() {
  if !h.airportMediator.CanLandAirport(h) { // 申请塔台是否能够起飞
    fmt.Printf("机场忙碌,直升机%s持续期待起飞;\n", h.name)
    return
  }
  fmt.Printf("直升机%s胜利垂直起飞机场;\n", h.name)
}

func (h *helicopter) DepartAirport() {
  fmt.Printf("直升机%s胜利垂直腾飞,来到机场;\n", h.name)
  h.airportMediator.NotifyWaitingAircraft() // 告诉其余期待起飞的飞机
}

(四)机场塔台

package mediator

// AirportMediator 机场调度中介者
type AirportMediator interface {
  CanLandAirport(aircraft Aircraft) bool // 确认是否能够起飞
  NotifyWaitingAircraft()                // 告诉期待起飞的其余飞机
}

// ApproachTower 机场塔台
type ApproachTower struct {
  hasFreeAirstrip bool
  waitingQueue    []Aircraft // 期待起飞的飞机队列
}

func (a *ApproachTower) CanLandAirport(aircraft Aircraft) bool {
  if a.hasFreeAirstrip {
    a.hasFreeAirstrip = false
    return true
  }
  // 没有空余的跑道,退出期待队列
  a.waitingQueue = append(a.waitingQueue, aircraft)
  return false
}

func (a *ApproachTower) NotifyWaitingAircraft() {
  if !a.hasFreeAirstrip {
    a.hasFreeAirstrip = true
  }
  if len(a.waitingQueue) > 0 {
    // 如果存在期待起飞的飞机,告诉第一个起飞
    first := a.waitingQueue[0]
    a.waitingQueue = a.waitingQueue[1:]
    first.ApproachAirport()
  }
}

(五)测试程序

package mediator

import "testing"

func TestMediator(t *testing.T) {
  // 创立机场调度塔台
  airportMediator := &ApproachTower{hasFreeAirstrip: true}
  // 创立C919客机
  c919Airliner := NewAirliner("C919", airportMediator)
  // 创立米-26重型运输直升机
  m26Helicopter := NewHelicopter("米-26", airportMediator)

  c919Airliner.ApproachAirport()  // c919进港起飞
  m26Helicopter.ApproachAirport() // 米-26进港期待

  c919Airliner.DepartAirport()  // c919飞离,期待的米-26进港起飞
  m26Helicopter.DepartAirport() // 最初米-26飞离
}

(六)运行后果

=== RUN   TestMediator
客机C919胜利滑翔起飞机场;
机场忙碌,直升机米-26持续期待起飞;
客机C919胜利滑翔腾飞,来到机场;
直升机米-26胜利垂直起飞机场;
直升机米-26胜利垂直腾飞,来到机场;
--- PASS: TestMediator (0.00s)
PASS

备忘录模式

(一)概念

备忘录模式是一种行为设计模式, 容许在不裸露对象实现细节的状况下保留和复原对象之前的状态。

备忘录不会影响它所解决的对象的内部结构, 也不会影响快照中保留的数据。

个别状况由原发对象保留生成的备忘录对象的状态不能被除原发对象之外的对象拜访,所以通过外部类定义具体的备忘录对象是比拟平安的,然而go语言不反对外部类定义的形式,因而go语言实现备忘录对象时,首先将备忘录保留的状态设为非导出字段,防止内部对象拜访,其次将原发对象的援用保留到备忘录对象中,当通过备忘录对象复原时,间接操作备忘录的复原办法,将备份数据状态设置到原发对象中,实现复原。

(二)示例

大家平时玩的角色扮演闯关游戏的存档机制就能够通过备忘录模式实现,每到一个要害关卡,玩家常常会先保留游戏存档,用于闯关失败后重置,存档会把角色状态及场景状态保留到备忘录中,同时将须要复原游戏的援用存入备忘录,用于关卡重置;

(三)闯关游戏

package memento

import "fmt"

// Originator 备忘录模式原发器接口
type Originator interface {
  Save(tag string) Memento // 以后状态保留备忘录
}

// RolesPlayGame 反对存档的RPG游戏
type RolesPlayGame struct {
  name          string   // 游戏名称
  rolesState    []string // 游戏角色状态
  scenarioState string   // 游戏场景状态
}

// NewRolesPlayGame 依据游戏名称和角色名,创立RPG游戏
func NewRolesPlayGame(name string, roleName string) *RolesPlayGame {
  return &RolesPlayGame{
    name:          name,
    rolesState:    []string{roleName, "血量100"}, // 默认满血
    scenarioState: "开始通过第一关",                   // 默认第一关开始
  }
}

// Save 保留RPG游戏角色状态及场景状态到指定标签归档
func (r *RolesPlayGame) Save(tag string) Memento {
  return newRPGArchive(tag, r.rolesState, r.scenarioState, r)
}

func (r *RolesPlayGame) SetRolesState(rolesState []string) {
  r.rolesState = rolesState
}

func (r *RolesPlayGame) SetScenarioState(scenarioState string) {
  r.scenarioState = scenarioState
}

// String 输入RPG游戏简要信息
func (r *RolesPlayGame) String() string {
  return fmt.Sprintf("在%s游戏中,玩家应用%s,%s,%s;", r.name, r.rolesState[0], r.rolesState[1], r.scenarioState)
}

(四)游戏存档

package memento

import "fmt"

// Memento 备忘录接口
type Memento interface {
  Tag() string // 备忘录标签
  Restore()    // 依据备忘录存储数据状态恢复原对象
}

// rpgArchive rpg游戏存档,
type rpgArchive struct {
  tag           string         // 存档标签
  rolesState    []string       // 存档的角色状态
  scenarioState string         // 存档游戏场景状态
  rpg           *RolesPlayGame // rpg游戏援用
}

// newRPGArchive 依据标签,角色状态,场景状态,rpg游戏援用,创立游戏归档备忘录
func newRPGArchive(tag string, rolesState []string, scenarioState string, rpg *RolesPlayGame) *rpgArchive {
  return &rpgArchive{
    tag:           tag,
    rolesState:    rolesState,
    scenarioState: scenarioState,
    rpg:           rpg,
  }
}

func (r *rpgArchive) Tag() string {
  return r.tag
}

// Restore 依据归档数据恢复游戏状态
func (r *rpgArchive) Restore() {
  r.rpg.SetRolesState(r.rolesState)
  r.rpg.SetScenarioState(r.scenarioState)
}

// RPGArchiveManager RPG游戏归档管理器
type RPGArchiveManager struct {
  archives map[string]Memento // 存储归档标签对应归档
}

func NewRPGArchiveManager() *RPGArchiveManager {
  return &RPGArchiveManager{
    archives: make(map[string]Memento),
  }
}

// Reload 依据标签从新加载归档数据
func (r *RPGArchiveManager) Reload(tag string) {
  if archive, ok := r.archives[tag]; ok {
    fmt.Printf("从新加载%s;\n", tag)
    archive.Restore()
  }
}

// Put 保留归档数据
func (r *RPGArchiveManager) Put(memento Memento) {
  r.archives[memento.Tag()] = memento
}

(五)测试程序

package memento

import (
  "fmt"
  "testing"
)

func TestMemento(t *testing.T) {
  // 创立RPG游戏存档管理器
  rpgManager := NewRPGArchiveManager()
  // 创立RPG游戏
  rpg := NewRolesPlayGame("暗黑破坏神2", "横蛮人兵士")
  fmt.Println(rpg)                  // 输入游戏以后状态
  rpgManager.Put(rpg.Save("第一关存档")) // 游戏存档

  // 第一关闯关失败
  rpg.SetRolesState([]string{"横蛮人兵士", "死亡"})
  rpg.SetScenarioState("第一关闯关失败")
  fmt.Println(rpg)

  // 复原存档,从新闯关
  rpgManager.Reload("第一关存档")
  fmt.Println(rpg)
}

(六)运行后果

=== RUN   TestMemento
在暗黑破坏神2游戏中,玩家应用横蛮人兵士,血量100,开始通过第一关;
在暗黑破坏神2游戏中,玩家应用横蛮人兵士,死亡,第一关闯关失败;
从新加载第一关存档;
在暗黑破坏神2游戏中,玩家应用横蛮人兵士,血量100,开始通过第一关;
--- PASS: TestMemento (0.00s)
PASS

观察者模式

(一)概念

观察者模式是一种行为设计模式,容许你定义一种订阅机制,可在对象事件产生时告诉多个 “察看” 该对象的其余对象。

观察者模式提供了一种作用于任何实现了订阅者接口的对象的机制,可对其事件进行订阅和勾销订阅。

观察者模式是最罕用的模式之一,是事件总线,分布式消息中间件等各种事件机制的原始实践根底,罕用于解耦多对一的对象依赖关系;

加强的实现性能包含:

  • 当被观察者通过异步实现告诉多个观察者时就相当于单过程实例的音讯总线;
  • 同时还能够依据业务须要,将被观察者所有数据状态变更进行分类为不同的主题,观察者通过不同主题进行订阅;
  • 同一个主题又可分为减少,删除,批改事件行为;
  • 每个主题能够实现一个线程池,多个主题通过不同的线程池进行解决隔离,线程池能够设置并发线程大小、缓冲区大小及调度策略,比方先进先出,优先级等策略;
  • 观察者处理事件时有可能出现异常,所以也能够注册异样处理函数,异样解决也能够通过异样类型进行分类;
  • 依据业务需要也能够实现告诉异样重试,提早告诉等性能;

(二)示例

信用卡业务音讯揭示可通过观察者模式实现,业务音讯包含日常生产,出账单,账单逾期,音讯揭示包含短信、邮件及电话,依据不同业务的场景会采纳不同的音讯揭示形式或者多种音讯揭示形式,这里信用卡相当于被观察者,观察者相当于不同的告诉形式;日常生产通过短信告诉,出账单通过邮件告诉,账单逾期三种形式都会进行告诉;

(三)告诉形式

package observer

import "fmt"

// Subscriber 订阅者接口
type Subscriber interface {
  Name() string          //订阅者名称
  Update(message string) //订阅更新办法
}

// shortMessage 信用卡音讯短信订阅者
type shortMessage struct{}

func (s *shortMessage) Name() string {
  return "手机短息"
}

func (s *shortMessage) Update(message string) {
  fmt.Printf("通过【%s】发送音讯:%s\n", s.Name(), message)
}

// email 信用卡音讯邮箱订阅者
type email struct{}

func (e *email) Name() string {
  return "电子邮件"
}

func (e *email) Update(message string) {
  fmt.Printf("通过【%s】发送音讯:%s\n", e.Name(), message)
}

// telephone 信用卡音讯电话订阅者
type telephone struct{}

func (t *telephone) Name() string {
  return "电话"
}

func (t *telephone) Update(message string) {
  fmt.Printf("通过【%s】告知:%s\n", t.Name(), message)
}

(四)信用卡业务

package observer

import "fmt"

// MsgType 信用卡音讯类型
type MsgType int

const (
  ConsumeType MsgType = iota // 生产音讯类型
  BillType                   // 账单音讯类型
  ExpireType                 // 逾期音讯类型
)

// CreditCard 信用卡
type CreditCard struct {
  holder          string                   // 持卡人
  consumeSum      float32                  // 生产总金额
  subscriberGroup map[MsgType][]Subscriber // 依据音讯类型分组订阅者
}

// NewCreditCard 指定持卡人创立信用卡
func NewCreditCard(holder string) *CreditCard {
  return &CreditCard{
    holder:          holder,
    subscriberGroup: make(map[MsgType][]Subscriber),
  }
}

// Subscribe 反对订阅多种音讯类型
func (c *CreditCard) Subscribe(subscriber Subscriber, msgTypes ...MsgType) {
  for _, msgType := range msgTypes {
    c.subscriberGroup[msgType] = append(c.subscriberGroup[msgType], subscriber)
  }
}

// Unsubscribe 解除订阅多种音讯类型
func (c *CreditCard) Unsubscribe(subscriber Subscriber, msgTypes ...MsgType) {
  for _, msgType := range msgTypes {
    if subs, ok := c.subscriberGroup[msgType]; ok {
      c.subscriberGroup[msgType] = removeSubscriber(subs, subscriber)
    }
  }
}

func removeSubscriber(subscribers []Subscriber, toRemove Subscriber) []Subscriber {
  length := len(subscribers)
  for i, subscriber := range subscribers {
    if toRemove.Name() == subscriber.Name() {
      subscribers[length-1], subscribers[i] = subscribers[i], subscribers[length-1]
      return subscribers[:length-1]
    }
  }
  return subscribers
}

// Consume 信用卡生产
func (c *CreditCard) Consume(money float32) {
  c.consumeSum += money
  c.notify(ConsumeType, fmt.Sprintf("尊敬的持卡人%s,您以后生产%.2f元;", c.holder, money))
}

// SendBill 发送信用卡账单
func (c *CreditCard) SendBill() {
  c.notify(BillType, fmt.Sprintf("尊敬的持卡人%s,您本月账单已出,生产总额%.2f元;", c.holder, c.consumeSum))
}

// Expire 逾期告诉
func (c *CreditCard) Expire() {
  c.notify(ExpireType, fmt.Sprintf("尊敬的持卡人%s,您本月账单已逾期,请及时还款,总额%.2f元;", c.holder, c.consumeSum))
}

// notify 依据音讯类型告诉订阅者
func (c *CreditCard) notify(msgType MsgType, message string) {
  if subs, ok := c.subscriberGroup[msgType]; ok {
    for _, sub := range subs {
      sub.Update(message)
    }
  }
}

(五)测试程序

package observer

import "testing"

func TestObserver(t *testing.T) {
  // 创立张三的信用卡
  creditCard := NewCreditCard("张三")
  // 短信告诉订阅信用卡生产及逾期音讯
  creditCard.Subscribe(new(shortMessage), ConsumeType, ExpireType)
  // 电子邮件告诉订阅信用卡账单及逾期音讯
  creditCard.Subscribe(new(email), BillType, ExpireType)
  // 电话告诉订阅信用卡逾期音讯,同时逾期音讯通过三种形式告诉
  creditCard.Subscribe(new(telephone), ExpireType)

  creditCard.Consume(500.00) // 信用卡生产
  creditCard.Consume(800.00) // 信用卡生产
  creditCard.SendBill()      // 信用卡发送账单
  creditCard.Expire()        // 信用卡逾期

  // 信用卡逾期音讯勾销电子邮件及短信告诉订阅
  creditCard.Unsubscribe(new(email), ExpireType)
  creditCard.Unsubscribe(new(shortMessage), ExpireType)
  creditCard.Consume(300.00) // 信用卡生产
  creditCard.Expire()        // 信用卡逾期
}

(六)运行后果

=== RUN   TestObserver
通过【手机短息】发送音讯:尊敬的持卡人张三,您以后生产500.00元;
通过【手机短息】发送音讯:尊敬的持卡人张三,您以后生产800.00元;
通过【电子邮件】发送音讯:尊敬的持卡人张三,您本月账单已出,生产总额1300.00元;
通过【手机短息】发送音讯:尊敬的持卡人张三,您本月账单已逾期,请及时还款,总额1300.00元;
通过【电子邮件】发送音讯:尊敬的持卡人张三,您本月账单已逾期,请及时还款,总额1300.00元;
通过【电话】告知:尊敬的持卡人张三,您本月账单已逾期,请及时还款,总额1300.00元;
通过【手机短息】发送音讯:尊敬的持卡人张三,您以后生产300.00元;
通过【电话】告知:尊敬的持卡人张三,您本月账单已逾期,请及时还款,总额1600.00元;
--- PASS: TestObserver (0.00s)
PASS

状态模式

(一)概念

状态模式是一种行为设计模式,让你能在一个对象的外部状态变动时扭转其行为,使其看上去就像扭转了本身所属的类一样。

该模式将与状态相干的行为抽取到独立的状态类中,让原对象将工作委派给这些类的实例,而不是自行进行解决。

状态迁徙有四个元素组成,起始状态、触发迁徙的事件,终止状态以及要执行的动作,每个具体的状态蕴含触发状态迁徙的执行办法,迁徙办法的实现是执行持有状态对象的动作办法,同时设置状态为下一个流转状态;持有状态的业务对象蕴含有触发状态迁徙办法,这些迁徙办法将申请委托给以后具体状态对象的迁徙办法。

(二)示例

IPhone手机充电就是一个手机电池状态的流转,一开始手机处于有电状态,插入充电插头后,持续充电到满电状态,并进入断电爱护,插入充电插头后应用手机,由满电逐步变为没电,最终关机;

状态迁徙表:

(三)电池状态

package state

import "fmt"

// BatteryState 电池状态接口,反对手机充电线插拔事件
type BatteryState interface {
  ConnectPlug(iPhone *IPhone) string
  DisconnectPlug(iPhone *IPhone) string
}

// fullBatteryState 满电状态
type fullBatteryState struct{}

func (s *fullBatteryState) String() string {
  return "满电状态"
}

func (s *fullBatteryState) ConnectPlug(iPhone *IPhone) string {
  return iPhone.pauseCharge()
}

func (s *fullBatteryState) DisconnectPlug(iPhone *IPhone) string {
  iPhone.SetBatteryState(PartBatteryState)
  return fmt.Sprintf("%s,%s转为%s", iPhone.consume(), s, PartBatteryState)
}

// emptyBatteryState 空电状态
type emptyBatteryState struct{}

func (s *emptyBatteryState) String() string {
  return "没电状态"
}

func (s *emptyBatteryState) ConnectPlug(iPhone *IPhone) string {
  iPhone.SetBatteryState(PartBatteryState)
  return fmt.Sprintf("%s,%s转为%s", iPhone.charge(), s, PartBatteryState)
}

func (s *emptyBatteryState) DisconnectPlug(iPhone *IPhone) string {
  return iPhone.shutdown()
}

// partBatteryState 局部电状态
type partBatteryState struct{}

func (s *partBatteryState) String() string {
  return "有电状态"
}

func (s *partBatteryState) ConnectPlug(iPhone *IPhone) string {
  iPhone.SetBatteryState(FullBatteryState)
  return fmt.Sprintf("%s,%s转为%s", iPhone.charge(), s, FullBatteryState)
}

func (s *partBatteryState) DisconnectPlug(iPhone *IPhone) string {
  iPhone.SetBatteryState(EmptyBatteryState)
  return fmt.Sprintf("%s,%s转为%s", iPhone.consume(), s, EmptyBatteryState)
}

(四)IPhone手机

package state

import "fmt"

// 电池状态单例,全局对立应用三个状态的单例,不须要反复创立
var (
  FullBatteryState  = new(fullBatteryState)  // 满电
  EmptyBatteryState = new(emptyBatteryState) // 空电
  PartBatteryState  = new(partBatteryState)  // 局部电
)

// IPhone 已手机充电为例,实现状态模式
type IPhone struct {
  model        string       // 手机型号
  batteryState BatteryState // 电池状态
}

// NewIPhone 创立指定型号手机
func NewIPhone(model string) *IPhone {
  return &IPhone{
    model:        model,
    batteryState: PartBatteryState,
  }
}

// BatteryState 输入电池以后状态
func (i *IPhone) BatteryState() string {
  return fmt.Sprintf("iPhone %s 以后为%s", i.model, i.batteryState)
}

// ConnectPlug 连贯充电线
func (i *IPhone) ConnectPlug() string {
  return fmt.Sprintf("iPhone %s 连贯电源线,%s", i.model, i.batteryState.ConnectPlug(i))
}

// DisconnectPlug 断开充电线
func (i *IPhone) DisconnectPlug() string {
  return fmt.Sprintf("iPhone %s 断开电源线,%s", i.model, i.batteryState.DisconnectPlug(i))
}

// SetBatteryState 设置电池状态
func (i *IPhone) SetBatteryState(state BatteryState) {
  i.batteryState = state
}

func (i *IPhone) charge() string {
  return "正在充电"
}

func (i *IPhone) pauseCharge() string {
  return "电已满,暂停充电"
}

func (i *IPhone) shutdown() string {
  return "手机敞开"
}

func (i *IPhone) consume() string {
  return "应用中,耗费电量"
}

(五)测试程序

package state

import (
  "fmt"
  "testing"
)

func TestState(t *testing.T) {
  iPhone13Pro := NewIPhone("13 pro") // 刚创立的手机有局部电

  fmt.Println(iPhone13Pro.BatteryState()) // 打印局部电状态
  fmt.Println(iPhone13Pro.ConnectPlug())  // 插上电源插头,持续充斥电
  fmt.Println(iPhone13Pro.ConnectPlug())  // 满电后再充电,会触发满电爱护

  fmt.Println(iPhone13Pro.DisconnectPlug()) // 拔掉电源,应用手机耗费电量,变为有局部电
  fmt.Println(iPhone13Pro.DisconnectPlug()) // 始终应用手机,直到没电
  fmt.Println(iPhone13Pro.DisconnectPlug()) // 没电后会关机

  fmt.Println(iPhone13Pro.ConnectPlug()) // 再次插上电源一会,变为有电状态
}

(六)运行后果

=== RUN   TestState
iPhone 13 pro 以后为有电状态
iPhone 13 pro 连贯电源线,正在充电,有电状态转为满电状态
iPhone 13 pro 连贯电源线,电已满,暂停充电
iPhone 13 pro 断开电源线,应用中,耗费电量,满电状态转为有电状态
iPhone 13 pro 断开电源线,应用中,耗费电量,有电状态转为没电状态
iPhone 13 pro 断开电源线,手机敞开
iPhone 13 pro 连贯电源线,正在充电,没电状态转为有电状态
--- PASS: TestState (0.00s)
PASS

策略模式

(一)概念

策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法别离放入独立的类中,以使算法的对象可能互相替换。

原始对象被称为上下文,它蕴含指向策略对象的援用并将执行行为的工作分派给策略对象。为了扭转上下文实现其工作的形式,其余对象能够应用另一个对象来替换以后链接的策略对象。

策略模式是最罕用的设计模式,也是比较简单的设计模式,是以多态替换条件表达式重构办法的具体实现,是面向接口编程准则的最间接体现;

(二)示例

北京是一个四季明显的城市,每个节令天气情况都有显著特点;咱们定义一个显示天气情况的节令接口,具体的四季实现,都会保留一个城市和天气情况的映射表,城市对象会蕴含节令接口,随着四季的变动,天气情况也随之变动;

(三)四季天气

package strategy

import "fmt"

// Season 节令的策略接口,不同节令体现得天气不同
type Season interface {
  ShowWeather(city string) string // 显示指定城市的天气情况
}

type spring struct {
  weathers map[string]string // 存储不同城市春天气象
}

func NewSpring() *spring {
  return &spring{
    weathers: map[string]string{"北京": "干燥多风", "昆明": "清凉舒服"},
  }
}

func (s *spring) ShowWeather(city string) string {
  return fmt.Sprintf("%s的春天,%s;", city, s.weathers[city])
}

type summer struct {
  weathers map[string]string // 存储不同城市夏天气象
}

func NewSummer() *summer {
  return &summer{
    weathers: map[string]string{"北京": "高温多雨", "昆明": "清凉舒服"},
  }
}

func (s *summer) ShowWeather(city string) string {
  return fmt.Sprintf("%s的夏天,%s;", city, s.weathers[city])
}

type autumn struct {
  weathers map[string]string // 存储不同城市秋天气象
}

func NewAutumn() *autumn {
  return &autumn{
    weathers: map[string]string{"北京": "凉快舒服", "昆明": "清凉舒服"},
  }
}

func (a *autumn) ShowWeather(city string) string {
  return fmt.Sprintf("%s的秋天,%s;", city, a.weathers[city])
}

type winter struct {
  weathers map[string]string // 存储不同城市冬天气象
}

func NewWinter() *winter {
  return &winter{
    weathers: map[string]string{"北京": "干燥凛冽", "昆明": "清凉舒服"},
  }
}

func (w *winter) ShowWeather(city string) string {
  return fmt.Sprintf("%s的冬天,%s;", city, w.weathers[city])
}

(四)城市气候

package strategy

import (
  "fmt"
)

// City 城市
type City struct {
  name    string
  feature string
  season  Season
}

// NewCity 依据名称及季候特色创立城市
func NewCity(name, feature string) *City {
  return &City{
    name:    name,
    feature: feature,
  }
}

// SetSeason 设置不同节令,相似天气在不同节令的不同策略
func (c *City) SetSeason(season Season) {
  c.season = season
}

// String 显示城市的气象信息
func (c *City) String() string {
  return fmt.Sprintf("%s%s,%s", c.name, c.feature, c.season.ShowWeather(c.name))
}

(五)测试程序

package strategy

import (
  "fmt"
  "testing"
)

func TestStrategy(t *testing.T) {
  Beijing := NewCity("北京", "四季明显")

  Beijing.SetSeason(NewSpring())
  fmt.Println(Beijing)

  Beijing.SetSeason(NewSummer())
  fmt.Println(Beijing)

  Beijing.SetSeason(NewAutumn())
  fmt.Println(Beijing)

  Beijing.SetSeason(NewWinter())
  fmt.Println(Beijing)
}

(六)运行后果

=== RUN   TestStrategy
北京四季明显,北京的春天,干燥多风;
北京四季明显,北京的夏天,高温多雨;
北京四季明显,北京的秋天,凉快舒服;
北京四季明显,北京的冬天,干燥凛冽;
--- PASS: TestStrategy (0.00s)
PASS

模板办法模式

(一)概念

模板办法模式是一种行为设计模式,它在超类中定义了一个算法的框架,容许子类在不批改构造的状况下重写算法的特定步骤。

因为GO语言没有继承的语法,模板办法又是依赖继承实现的设计模式,因而GO语言实现模板办法比拟艰难, GO语言反对隐式内嵌字段“继承”其余构造体的字段与办法,然而这个并不是真正意义上的继承语法,外层构造重写隐式字段中的算法特定步骤后,无奈动静绑定到“继承”过去的算法的框架办法调用中,因而不能实现模板办法模式的语义。

(二)示例

本示例给出一种间接实现模板办法的形式,也比拟合乎模板办法模式的定义:

  • 将多个算法特定步骤组合成一个接口;
  • 基类隐式内嵌算法步骤接口,同时调用算法步骤接口的各办法,实现算法的模板办法,此时基类内嵌的算法步骤接口并没有真正的解决行为;
  • 子类隐式内嵌基类,并覆写算法步骤接口的办法;
  • 通过工厂办法创立具体子类,并将本人的援用赋值给基类中算法步骤接口字段;

以演员打扮为例,演员的打扮是分为化妆,穿衣,配饰三步骤,三个步骤又依据不同角色的演员有所差异,因而演员基类实现打扮的模板办法,对于化妆,穿衣,配饰的三个步骤,在子类演员中具体实现,子类具体演员分为,男演员、女演员和儿童演员;

(三)演员基类

package templatemethod

import (
  "bytes"
  "fmt"
)

// IActor 演员接口
type IActor interface {
  DressUp() string // 打扮
}

// dressBehavior 打扮的多个行为,这里多个行为是公有的,通过DressUp模版办法调用
type dressBehavior interface {
  makeUp() string // 化妆
  clothe() string // 穿衣
  wear() string   // 配饰
}

// BaseActor 演员基类
type BaseActor struct {
  roleName      string // 表演角色
  dressBehavior        // 打扮行为
}

// DressUp 对立实现演员接口的DressUp模版办法,打扮过程通过不同打扮行为进行扩大
func (b *BaseActor) DressUp() string {
  buf := bytes.Buffer{}
  buf.WriteString(fmt.Sprintf("表演%s的", b.roleName))
  buf.WriteString(b.makeUp())
  buf.WriteString(b.clothe())
  buf.WriteString(b.wear())
  return buf.String()
}

(四)具体演员

package templatemethod

// womanActor 扩大打扮行为的女演员
type womanActor struct {
  BaseActor
}

// NewWomanActor 指定角色创立女演员
func NewWomanActor(roleName string) *womanActor {
  actor := new(womanActor)    // 创立女演员
  actor.roleName = roleName   // 设置角色
  actor.dressBehavior = actor // 将女演员实现的扩大打扮行为,设置给本人的打扮行为接口
  return actor
}

// 化妆
func (w *womanActor) makeUp() string {
  return "女演员涂着口红,画着眉毛;"
}

// 穿衣
func (w *womanActor) clothe() string {
  return "衣着连衣裙;"
}

// 配饰
func (w *womanActor) wear() string {
  return "带着耳环,手拎着包;"
}

// manActor 扩大打扮行为的男演员
type manActor struct {
  BaseActor
}

func NewManActor(roleName string) *manActor {
  actor := new(manActor)
  actor.roleName = roleName
  actor.dressBehavior = actor // 将男演员实现的扩大打扮行为,设置给本人的打扮行为接口
  return actor
}

func (m *manActor) makeUp() string {
  return "男演员刮净胡子,抹上发胶;"
}

func (m *manActor) clothe() string {
  return "衣着一身西装;"
}

func (m *manActor) wear() string {
  return "带上手表,抽着烟;"
}

// NewChildActor 扩大打扮行为的儿童演员
type childActor struct {
  BaseActor
}

func NewChildActor(roleName string) *childActor {
  actor := new(childActor)
  actor.roleName = roleName
  actor.dressBehavior = actor // 将儿童演员实现的扩大打扮行为,设置给本人的打扮行为接口
  return actor
}

func (c *childActor) makeUp() string {
  return "儿童演员抹上红脸蛋;"
}

func (c *childActor) clothe() string {
  return "衣着一身童装;"
}

func (c *childActor) wear() string {
  return "手里拿着一串糖葫芦;"
}

(五)测试程序

package templatemethod

import (
  "fmt"
  "testing"
)

func TestTemplateMethod(t *testing.T) {
  showActors(NewWomanActor("妈妈"), NewManActor("爸爸"), NewChildActor("儿子"))
}

// showActors 显示演员的打扮信息
func showActors(actors ...IActor) {
  for _, actor := range actors {
    fmt.Println(actor.DressUp())
  }
}

(六)运行后果

=== RUN   TestTemplateMethod
表演妈妈的女演员涂着口红,画着眉毛;衣着连衣裙;带着耳环,手拎着包;
表演爸爸的男演员刮净胡子,抹上发胶;衣着一身西装;带上手表,抽着烟;
表演儿子的儿童演员抹上红脸蛋;衣着一身童装;手里拿着一串糖葫芦;
--- PASS: TestTemplateMethod (0.00s)
PASS

访问者模式

(一)概念

访问者模式是一种行为设计模式,它能将算法与其所作用的对象隔离开来。容许你在不批改已有代码的状况下向已有类层次结构中减少新的行为。

访问者接口须要依据被访问者具体类,定义多个类似的拜访办法,每个具体类对应一个拜访办法;每个被访问者须要实现一个承受访问者对象的办法,办法的实现就是去调用访问者接口对应该类的拜访办法;这个承受办法能够传入不同目标访问者接口的具体实现,从而在不批改被拜访对象的前提下,减少新的性能;

(二)示例

公司中存在多种类型的员工,包含产品经理、软件工程师、人力资源等,他们的KPI指标不尽相同,产品经理为上线产品数量及满意度,软件工程师为实现的需要数及批改bug数,人力资源为招聘员工的数量;公司要依据员工实现的KPI进行表彰公示,同时依据KPI实现状况定薪酬,这些性能都是员工类职责之外的,不能批改员工自身的类,咱们通过访问者模式,实现KPI表彰排名及薪酬发放;

(三)员工构造

package visitor

import "fmt"

// Employee 员工接口
type Employee interface {
  KPI() string                    // 实现kpi信息
  Accept(visitor EmployeeVisitor) // 承受访问者对象
}

// productManager 产品经理
type productManager struct {
  name         string // 名称
  productNum   int    // 上线产品数
  satisfaction int    // 均匀满意度
}

func NewProductManager(name string, productNum int, satisfaction int) *productManager {
  return &productManager{
    name:         name,
    productNum:   productNum,
    satisfaction: satisfaction,
  }
}

func (p *productManager) KPI() string {
  return fmt.Sprintf("产品经理%s,上线%d个产品,均匀满意度为%d", p.name, p.productNum, p.satisfaction)
}

func (p *productManager) Accept(visitor EmployeeVisitor) {
  visitor.VisitProductManager(p)
}

// softwareEngineer 软件工程师
type softwareEngineer struct {
  name           string // 姓名
  requirementNum int    // 实现需要数
  bugNum         int    // 修复问题数
}

func NewSoftwareEngineer(name string, requirementNum int, bugNum int) *softwareEngineer {
  return &softwareEngineer{
    name:           name,
    requirementNum: requirementNum,
    bugNum:         bugNum,
  }
}

func (s *softwareEngineer) KPI() string {
  return fmt.Sprintf("软件工程师%s,实现%d个需要,修复%d个问题", s.name, s.requirementNum, s.bugNum)
}

func (s *softwareEngineer) Accept(visitor EmployeeVisitor) {
  visitor.VisitSoftwareEngineer(s)
}

// hr 人力资源
type hr struct {
  name       string // 姓名
  recruitNum int    // 招聘人数
}

func NewHR(name string, recruitNum int) *hr {
  return &hr{
    name:       name,
    recruitNum: recruitNum,
  }
}

func (h *hr) KPI() string {
  return fmt.Sprintf("人力资源%s,招聘%d名员工", h.name, h.recruitNum)
}

func (h *hr) Accept(visitor EmployeeVisitor) {
  visitor.VisitHR(h)
}

(四)员工访问者

package visitor

import (
  "fmt"
  "sort"
)

// EmployeeVisitor 员工访问者接口
type EmployeeVisitor interface {
  VisitProductManager(pm *productManager)     // 拜访产品经理
  VisitSoftwareEngineer(se *softwareEngineer) // 拜访软件工程师
  VisitHR(hr *hr)                             // 拜访人力资源
}

// kpi kpi对象
type kpi struct {
  name string // 实现kpi姓名
  sum  int    // 实现kpi总数量
}

// kpiTopVisitor 员工kpi排名访问者
type kpiTopVisitor struct {
  top []*kpi
}

func (k *kpiTopVisitor) VisitProductManager(pm *productManager) {
  k.top = append(k.top, &kpi{
    name: pm.name,
    sum:  pm.productNum + pm.satisfaction,
  })
}

func (k *kpiTopVisitor) VisitSoftwareEngineer(se *softwareEngineer) {
  k.top = append(k.top, &kpi{
    name: se.name,
    sum:  se.requirementNum + se.bugNum,
  })
}

func (k *kpiTopVisitor) VisitHR(hr *hr) {
  k.top = append(k.top, &kpi{
    name: hr.name,
    sum:  hr.recruitNum,
  })
}

// Publish 公布KPI排行榜
func (k *kpiTopVisitor) Publish() {
  sort.Slice(k.top, func(i, j int) bool {
    return k.top[i].sum > k.top[j].sum
  })
  for i, curKPI := range k.top {
    fmt.Printf("第%d名%s:实现KPI总数%d\n", i+1, curKPI.name, curKPI.sum)
  }
}

// salaryVisitor 薪酬访问者
type salaryVisitor struct{}

func (s *salaryVisitor) VisitProductManager(pm *productManager) {
  fmt.Printf("产品经理根本薪资:1000元,KPI单位薪资:100元,")
  fmt.Printf("%s,总工资为%d元\n", pm.KPI(), (pm.productNum+pm.satisfaction)*100+1000)
}

func (s *salaryVisitor) VisitSoftwareEngineer(se *softwareEngineer) {
  fmt.Printf("软件工程师根本薪资:1500元,KPI单位薪资:80元,")
  fmt.Printf("%s,总工资为%d元\n", se.KPI(), (se.requirementNum+se.bugNum)*80+1500)
}

func (s *salaryVisitor) VisitHR(hr *hr) {
  fmt.Printf("人力资源根本薪资:800元,KPI单位薪资:120元,")
  fmt.Printf("%s,总工资为%d元\n", hr.KPI(), hr.recruitNum*120+800)
}

(五)测试程序

package visitor

import "testing"

func TestVisitor(t *testing.T) {
  allEmployees := AllEmployees() // 获取所有员工
  kpiTop := new(kpiTopVisitor)   // 创立KPI排行访问者
  VisitAllEmployees(kpiTop, allEmployees)
  kpiTop.Publish() // 公布排行榜

  salary := new(salaryVisitor) // 创立薪酬访问者
  VisitAllEmployees(salary, allEmployees)
}

// VisitAllEmployees 遍历所有员工调用访问者
func VisitAllEmployees(visitor EmployeeVisitor, allEmployees []Employee) {
  for _, employee := range allEmployees {
    employee.Accept(visitor)
  }
}

// AllEmployees 取得所有公司员工
func AllEmployees() []Employee {
  var employees []Employee
  employees = append(employees, NewHR("小明", 10))
  employees = append(employees, NewProductManager("小红", 4, 7))
  employees = append(employees, NewSoftwareEngineer("张三", 10, 5))
  employees = append(employees, NewSoftwareEngineer("李四", 3, 6))
  employees = append(employees, NewSoftwareEngineer("王五", 7, 1))
  return employees
}

(六)运行后果

=== RUN   TestVisitor
第1名张三:实现KPI总数15
第2名小红:实现KPI总数11
第3名小明:实现KPI总数10
第4名李四:实现KPI总数9
第5名王五:实现KPI总数8
人力资源根本薪资:800元,KPI单位薪资:120元,人力资源小明,招聘10名员工,总工资为2000元
产品经理根本薪资:1000元,KPI单位薪资:100元,产品经理小红,上线4个产品,均匀满意度为7,总工资为2100元
软件工程师根本薪资:1500元,KPI单位薪资:80元,软件工程师张三,实现10个需要,修复5个问题,总工资为2700元
软件工程师根本薪资:1500元,KPI单位薪资:80元,软件工程师李四,实现3个需要,修复6个问题,总工资为2220元
软件工程师根本薪资:1500元,KPI单位薪资:80元,软件工程师王五,实现7个需要,修复1个问题,总工资为2140元
--- PASS: TestVisitor (0.00s)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理