行为型模式----golang实现

如果您觉得本篇文章不错,请记得到我的GitHub点上一个star,您的支持是我最大的动力!十分感谢!

命令模式

定义

命令模式( Command Pattern)又称为行动( Action)模式或交易( Transaction)模式。

命令模式的英文定义是:

Encapsulate a request as an object, thereby letting you parameterize clients withdifferent requests, queue or log requests, and support undoable operations.

意思是:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或,者记录请求日志,可以提供命令的撤销和恢复功能。

简单来说,命令模式就是将发送者、接收者和调用命令封装成对象,客户端调用的时候可以选择不同的对象,从而实现发送者和接收者的完全解耦。

类图

命令模式包含如下角色:

●命令接口(Command)角色:该角色声明一一个接口,定义需要执行的命令;

●具体命令实现类( Concrete Command) 角色:该角色定义一个接收者和行为之间的弱耦合,实现命令方法,并调用接收者的相应操作;

●调用者(Invoker) 角色:该角色负责调用命令对象执行请求;

●接收者( Receiver) 角色:该角色负责具体实施和执行请求动作(方法) ;

●客户端(Client)角色:串连执行整个流程。

优缺点

命令模式的优点:

  • 类间解耦:调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用Command中的execute()方法即可,不需要了解是哪个接收者执行;
  • 可扩展性: Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。

命令模式的缺点:

  • 使用命令模式会导致系统有过多的具体命令类,因为针对每一一个命令都需要设计一一个具体命令类。

应用场景

命令模式的典型应用场景如下:

  • 系统需要支持命令的撤销(undo),命令对象可以把状态存储起来,等到客户端需要撤销时,可以调用undo() 方法,将命令所产生的效果撤销;
  • 系统需要支持命令的撤销(Undo )操作和恢复( Redo)操作;
  • 系统需要将一-组操作组合在- 起,使用命令模式来实现, 可以很方便的增加新的命令。

使用实例:遥控器

import "fmt"
// 命令接口和实现类
type command interface {
	Execute()
}

type OpenTvCommand struct {
	tv *TV
}

func (o *OpenTvCommand) Execute()  {
	o.tv.Open()
}

type CloseTvCommand struct {
	tv *TV
}

func (c *CloseTvCommand) Execute()  {
	c.tv.Close()
}

type ChangeTvCommand struct {
	tv *TV
}

func (c *ChangeTvCommand) Execute()  {
	c.tv.Change()
}
// 命令的接收者
type TV struct {}

func (tv *TV) Open()  {
	fmt.Println("打开电视")
}
func (tv *TV) Close()  {
	fmt.Println("关闭电视")
}
func (tv *TV)Change()  {
	fmt.Println("换台")
}

// 命令的执行者:向TV发起命令
type TVRemote struct {
	open *OpenTvCommand
	change *ChangeTvCommand
	close *CloseTvCommand
}

func (tv *TVRemote) Open ()  {
	tv.open.Execute()
}
func (tv *TVRemote) Change()  {
	tv.change.Execute()
}
func (tv *TVRemote) Close ()  {
	tv.close.Execute()
}
// 创建接收者
	rece := &TV{}
	// 创建命令对象
	openComm := &OpenTvCommand{rece}
	changeComm := &ChangeTvCommand{rece}
	closeComm := &CloseTvCommand{rece}

	// 创建请求者,把命令对象设置进去
	tvR := &TVRemote{
		open:   openComm,
		change: changeComm,
		close:  closeComm,
	}
	tvR.Open()
	tvR.Change()
	tvR.Close()
打开电视
换台
关闭电视

中介者模式

定义

中介者?其实生活中大家再熟悉不过了这个词,我们熟悉的黄牛、房产中介等就是充当中介的角色,将我们的买票、购房等的需求自身消化再代为办理。又比如说中间件,马老师很忙,不能来,一个人有事就直接找马老师对吧,所以要找一个中介,客户来了直接找中间人,中间人再和马老师沟通,这样马老师和客户那边就是一个不可见的关系,由中介者角色进行中间协调,马老师也能抽出更多时间去忙别的事了,解放了相当的生产力。

中介者模式( Mediator)的定义:定义一个中介对象来封装对象之间的交互,使原有对象之间
耦合松散,并且可以独立地改变它们之间的交互。还记得迪米特法则吗?迪米特法则的初衷在于降低类之间的耦合,中介者模式就是迪米特法则的典型应用。

类图

中介者模式的组成角色如下:

  • 中介者(又称仲裁者,Mediator) :负责定义与Colleague 角色进行通信和做出角色的接
    口;
  • 具体中介者、仲裁者( ConcreteMediator) :负责实现Mediator角色定义的接口,负责具体的业务执行;
  • 同事角色(Colleague) :负责定义与Mediator角色进行通信的接口;
  • 具体同事角色(ConcreteColleague) :实现Colleague 角色定义的接口,一般会有多个实现类。

优缺点

中介者模式的优点:

  • 弱化对象间的依赖关系,即松耦合,降低同时类的耦合度,符合迪米特法则
  • 将对象间的调用关系进行封装,使得对象更容易复用
    中介者模式的缺点:
  • 如果对象增多,就要去修改抽象中介者和具体的中介者角色
  • 中介者角色承担了太多了业务逻辑功能,流程复杂时就会显得比较臃肿,不好管理

应用场景

中介者模式的应用场景一般比较明确,当系统有一系列对象需要相互调用,为弱化对象间的依赖关系,使得这些对象之间松耦合。

使用实例

生活中,最普遍熟悉的例子就是房屋中介或者qq群这种聊天案例,这里我们以房屋中介为例,中介公司就好比我们的中介者角色,而业主和买家就构成了两个不同的同事角色,买卖双方之间的这种交互就可以交给中介者去对接协调:

import (
	"fmt"
	"reflect"
)

// 抽象中介公司
type MeditorCompany interface {
	GetSeller() Colleaguer
	SetSeller(seller ColleagueSeller)
	GetBuyer() Colleaguer
	SetBuyer(ColleagueBuyer)
	GetName() string
	SetName(name string)
	Publish(message string,colleaguer Colleaguer)

}
// 具体中介者
type Meditor struct {
	name string
	buyer *ColleagueBuyer
	seller *ColleagueSeller
}

func (m *Meditor) SetSeller(seller ColleagueSeller) {
	m.seller = &seller
}

func (m *Meditor) SetBuyer(b ColleagueBuyer) {
	m.buyer = &b
}

func (m *Meditor) Publish(message string, colleaguer Colleaguer) {
	// 如果是卖家发布
	if reflect.DeepEqual(colleaguer,m.seller){
		m.buyer.Accept(message)
	} else if reflect.DeepEqual(colleaguer, m.buyer) {
		m.seller.Accept(message)
	}
}

func (m *Meditor) GetSeller() Colleaguer {
	return m.seller
}

func (m *Meditor) GetBuyer() Colleaguer {
	return m.buyer
}

func (m *Meditor) GetName() string {
	return m.name
}

func (m *Meditor) SetName(name string) {
	m.name = name
}

// 抽象同事角色
type Colleaguer interface {
	Colleguer(meditor MeditorCompany)
	Send(string)
	Accept(string)
}

// 卖家-同事角色
type ColleagueSeller struct {
	meditor MeditorCompany
}

func (c *ColleagueSeller) Send(message string) {
	c.meditor.Publish(message,c)
}

func (c *ColleagueSeller) Accept(message string) {
	fmt.Println("卖家收到的消息是"+message)
}

func (c *ColleagueSeller) Colleguer(meditor MeditorCompany) {
	c.meditor = meditor
}

// 买家-同事角色

type ColleagueBuyer struct {
	meditor MeditorCompany
}

func (c *ColleagueBuyer) Colleguer(meditor MeditorCompany) {
	c.meditor = meditor
}

func (c *ColleagueBuyer) Send(message string) {
	c.meditor.Publish(message,c)
}

func (c *ColleagueBuyer) Accept(message string) {
	fmt.Println("买家收到的消息是"+message)
}

	var (
		meitdor MeditorCompany
		seller *ColleagueSeller
		buyer *ColleagueBuyer
	)
	seller = &ColleagueSeller{meditor:meitdor}
	buyer = &ColleagueBuyer{meditor:meitdor}
	meitdor = &Meditor{
		name:   "58同城",
		buyer:  buyer,
		seller: seller,
	}
	// 卖家和卖家注册到中介
	seller.Colleguer(meitdor)
	buyer.Colleguer(meitdor)
	// 发布需求
	seller.Send("卖一套两室一厅100平米的Lofty")
	buyer.Send("求购一个两室一厅的房子")
=== RUN   TestColleagueSeller_Colleguer
买家收到的消息是卖一套两室一厅100平米的Lofty
卖家收到的消息是求购一个两室一厅的房子
--- PASS: TestColleagueSeller_Colleguer (0.00s)
PASS

备忘录模式

模式的定义与特点

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

模式的结构与实现

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。

  1. 模式的结构
    备忘录模式的主要角色如下。
  2. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  3. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  4. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录模式的结构图

模式的应用场景

  1. 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  2. 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
// 备忘录
type Memento struct {
	state string // 这里就是保存的状态
}

func (m *Memento) SetState(s string) {
	m.state = s
}

func (m *Memento) GetState() string {
	return m.state
}
// 发起人
type Originator struct {
	state string // 这里就简单一点,要保存的状态就是一个字符串
}

func (o *Originator) SetState(s string) {
	o.state = s
}

func (o *Originator) GetState() string {
	return o.state
}

// 这里就是规定了要保存的状态范围
func (o *Originator) CreateMemento() *Memento {
	return &Memento{state: o.state}
}
// 负责人
type Caretaker struct {
	memento *Memento
}

func (c *Caretaker) GetMemento() *Memento {
	return c.memento
}

func (c *Caretaker) SetMemento(m *Memento) {
	c.memento = m
}
    import "fmt"
    // 创建一个发起人并设置初始状态
	// 此时与备忘录模式无关,只是模拟正常程序运行
	o := &Originator{state: "hello"}
	fmt.Println("当前状态:",o.GetState())
	// 现在需要保存当前状态
	// 就创建一个负责人来设置(一般来说,对于一个对象的同一个备忘范围,应当只有一个负责人,这样方便做多状态多备忘管理)
	c := new(Caretaker)
	c.SetMemento(o.CreateMemento())

	o.SetState("world")
	fmt.Println("更改当前状态:",o.GetState())

	// 恢复备忘
	o.SetState(c.GetMemento().GetState())
	fmt.Println("恢复后状态",o.GetState())
当前状态: hello
更改当前状态: world
恢复后状态 hello

模板方法模式

定义

模板模式(Template Pattern )又被称作模板方法模式( Template Method Pattern),它是一种简单的、常见的且应用非常广泛的模式。

英文定义如下:

Define the skeleton of an algorithm in an operation, deferring some steps tosubclasses. Template Method lets subclasses redefine certain steps of analgorithm without changing the algorithm’ S structure.

意思是:定义一个操作中的算法的框架,而将一些 步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。简单来说,就是为子类设计一个模板,以便在子类中可以复用这些方法。

类图

模板模式包含如下角色:

  • 抽象模板(Abstract Template)角色:该角色定义一个或多个抽象操作,以便让子类实现;这些抽象操作是基本操作,是一个顶级逻辑的组成步骤,该角色还需要定义一个或几个模板方法(模板方法的介绍,请看下文) ;
  • 具体模板(Concrete Template) 角色:该角色实现抽象模板中定义的一个或多个抽象方法,每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。

优缺点

模板模式的优点:

  • 提高了代码的复用性,将相同部分的代码放在抽象的父类中;
  • 提高了拓展性:将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为;符合开闭原则:行为由父类控制,通过子类扩展新的行为。
    模板模式的缺点:
  • 每个不同的行为都要新增一个子类来完成,抽象类中的抽象方法越多,子类增加成本就越高。而且新增的子类越多,系统就越复杂。

应用场景

模板模式的典型应用场景如下:
多个子类有公共方法,并且逻辑基本相同时;

  • 可以把重要的、复杂的、核心算法设计为模板方法,其他的相关细节功能则由各个子类实现;
  • 重构时,模板方法模式是-一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

使用实例

以生活中.上班的过程为例,我们上班的通常流程是:起床洗漱->通勤(开车、坐公交、打车)
->到达公司。从以上步骤可以看出,只有通勤部分是不一样的,其他都一样,因为开车可能会
被限号,就只能打车或坐公交去公司了,下面我们用代码( 模板模式)来实现一下。

// 上班抽象模板接口
type AbstractWork interface {
    // 由于go是面向组合的一种思维,从语言层不支持聚合,所以聚合需要自己把接口变量传入来实现
	GotoWork(work AbstractWork)
	Getup()
	Commute()
	Arrive()
}
// 上班抽象类
type AbsClass struct {}

func (a AbsClass) GotoWork(work AbstractWork) {
	a.Getup()
	work.Commute()
	a.Arrive()
}

func (a AbsClass) Getup() {
	fmt.Println("1. 起床")
}

func (a AbsClass) Commute() {}

func (a AbsClass) Arrive() {
	fmt.Println("3. 到达")
}

type DriveToWork struct {
	AbsClass
}


func (d *DriveToWork) Commute() {
	fmt.Println("2. 开车去公司")
}

func (d *DriveToWork) GotoWork(work AbstractWork){
	d.AbsClass.GotoWork(d)
}

type BusToWork struct {
	AbsClass
}

func (d *BusToWork) Commute() {
	fmt.Println("2. 坐公交去公司")
}

func (d *BusToWork) GotoWork(work AbstractWork) {
	d.AbsClass.GotoWork(d)
}
var (
		work AbstractWork
	)
	work = &BusToWork{AbsClass{}}
	work.GotoWork(work)
	work = &DriveToWork{AbsClass{}}
	work.GotoWork(work)
=== RUN   TestAbsClass_GotoWork
1. 起床
2. 坐公交去公司
3. 到达
1. 起床
2. 开车去公司
3. 到达
--- PASS: TestAbsClass_GotoWork (0.00s)
PASS

状态模式

定义

状态模式( Allow an object to alter its behavior when its internal state changes.Theobject will appear to
change its class.)

翻译过来就是:允许一个对象在其内部状态改变时改变其行为,这个对象看起来好像是改变了其类。状态模式是一种对象行为型模式。

类图

状态模式包含角色如下:

  • 上下文角色( Context) :上下文角色- -般是一个类,上下文角色会聚合很多和state, 这些state使用静态常量修饰,并且负责state的状态切换;另外上下文角色还会包含抽象状态角色中定义的所有行为如request,然后内部将请求委托给state的handle处理;
  • 抽象状态角色(State) :抽象状态角色一般是一个抽象类,用来定义具体状态的公共行为比如handle,任何具体状态都必须实现该抽象类中的抽象方法;
  • 具体状态角色( ConcreteState) :继承抽象状态角色,实现抽象方法,实际处理来自Context的委托请求,当Context改变状态时行为也跟着改变。

优缺点

状态模式的优缺点总结如下:

  • 减少代码体积,利于拓展:状态模式可以消除繁杂的条件判断语句块,使得业务逻辑清晰,很好地应对对象状态的增加、删除的业务场景,因为添加新的状态只需要增加新的状态类就好了;

  • 状态模式状态很多时会导致状态类比较多,子类太多的时候就不方便维护管理了。

应用场景

状态模式的应用场景如下:

  • 行为随状态改变而改变的场景;
  • 化繁为简,如果代码中包含大量的条件语句块比如switch…case. if等,这些语句块的出现会导致业务逻辑变更时代码块也会变更,对状态的增加、删除时的调整修改起来比较吃力时就可以考虑状态模式;

使用实例,电视的操作

首先看看不引入状态模式时,我们要使用多少的switch case

import "fmt"
// 定义电视状态
const (
	STANDBY_STATE = 1
	POWER_OFF_STATE = 2
	PLAY_STATE = 3
)
type ITelevision interface {
	// 开机
	PowerOn()
	// 关机
	PowerOff()
	// 播放
	Play()
	// 待机
	Standby()
}
// 电视类
type Telev struct {
	ITelevision
	state int
}

func (telev *Telev) State() int {
	return telev.state
}

func (telev *Telev) SetState(state int) {
	telev.state = state
}

func (t *Telev) PowerOn() {
	switch t.state {
		case STANDBY_STATE:
		case POWER_OFF_STATE:
			fmt.Println("开机")
			t.SetState(STANDBY_STATE)
		case PLAY_STATE:
		default:
	}
}
func (t *Telev) PowerOff() {
	// 待机和播放状态都可以关机
	switch t.state {
	case STANDBY_STATE:
		fmt.Println("关机")
		t.SetState(POWER_OFF_STATE)
	case PLAY_STATE:
		fmt.Println("关机")
		t.SetState(POWER_OFF_STATE)
	case POWER_OFF_STATE:
	default:
	}
}
func (t *Telev) Play() {
	switch t.state {
	case STANDBY_STATE:
		fmt.Println("播放")
		t.SetState(PLAY_STATE)
	default:
	}
}
func (t *Telev) Standby() {
	switch t.state {
	case POWER_OFF_STATE:
		fmt.Println("关机")
		t.SetState(POWER_OFF_STATE)
	case PLAY_STATE:
		fmt.Println("待机")
		t.SetState(PLAY_STATE)
	default:
	}
}
tv := Telev{
		ITelevision: nil,
		state:       POWER_OFF_STATE,
	}
	// 这里因为电视还是关机状态,所以不会有任何的输出
	tv.Play()

	tv.PowerOn()
	tv.Play()
	tv.Standby()
	tv.PowerOff()
开机
播放
待机
关机
// 引入控制器(上下文角色)
type RemoteControlMachine struct {
	currentSate TVState
}

func (r *RemoteControlMachine) PowerOn() {
	r.currentSate.PowerOn(r)
}

func (r *RemoteControlMachine) PowerOff() {
	r.currentSate.PowerOff(r)
}

func (r *RemoteControlMachine) Play() {
	r.currentSate.Play(r)
}

func (r *RemoteControlMachine) Standby() {
	r.currentSate.Standby(r)
}

func (r *RemoteControlMachine) CurrentSate() TVState {
	return r.currentSate
}

func (r *RemoteControlMachine) SetCurrentSate(currentSate TVState) {
	r.currentSate = currentSate
}


// 电视状态抽象接口
type TVState interface {
	// 开机
	PowerOn(r *RemoteControlMachine)
	// 关机
	PowerOff(r *RemoteControlMachine)
	// 播放
	Play(r *RemoteControlMachine)
	// 待机
	Standby(r *RemoteControlMachine)
}

// 待机状态
type StandByState struct {
	r *RemoteControlMachine
}

func (s *StandByState) PowerOn(r *RemoteControlMachine) {}

func (s *StandByState) PowerOff(r *RemoteControlMachine) {
	fmt.Println("关机")
	// 使用遥控器设置电视机状态为关机
	s.r = r
	s.r.SetCurrentSate(&PowerOffState{})
	// 执行关机
	s.r.PowerOff()
}

func (s *StandByState) Play(r *RemoteControlMachine) {
	fmt.Println("播放")
	// 使用遥控器设置电视机状态为播放
	s.r = r
	s.r.SetCurrentSate(&PlayState{})
	// 执行播放
	s.r.Play()
}

func (s *StandByState) Standby(r *RemoteControlMachine) {
	// do nothing
}

// 关机状态
type PowerOffState struct {
	r *RemoteControlMachine
}

func (s *PowerOffState) PowerOn(r *RemoteControlMachine) {
	fmt.Println("开机")
	// 使用遥控器设置电视机状态为开机
	s.r = r
	s.r.SetCurrentSate(&StandByState{})
	// 执行播放
	s.r.Standby()
}

func (s *PowerOffState) PowerOff(r *RemoteControlMachine) {
}

func (s *PowerOffState) Play(r *RemoteControlMachine) {
}

func (s PowerOffState) Standby(r *RemoteControlMachine) {
}

// 播放状态
type PlayState struct {
	r *RemoteControlMachine
}

func (s *PlayState) PowerOn(r *RemoteControlMachine) {}

func (s *PlayState) PowerOff(r *RemoteControlMachine) {
	fmt.Println("关机")
	// 使用遥控器设置电视机状态为关机
	s.r = r
	s.r.SetCurrentSate(&PowerOffState{})
	// 执行关机
	s.r.PowerOff()
}

func (s *PlayState) Play(r *RemoteControlMachine) {
}

func (s *PlayState) Standby(r *RemoteControlMachine) {
	fmt.Println("开机")
	// 使用遥控器设置电视机状态为开机
	s.r = r
	s.r.SetCurrentSate(&StandByState{})
	// 执行播放
	s.r.Standby()
}


    context := RemoteControlMachine{}

	context.SetCurrentSate(&PowerOffState{})
	// 如果直接播放,因为电视处于关机状态,所以不会有输出
	context.Play()

	context.PowerOn()
	context.Play()
	context.Standby()
	context.PowerOff()
=== RUN   TestTelev_Play
开机
播放
开机
关机
--- PASS: TestTelev_Play (0.00s)
PASS

可以看到,测试结果没有任何不同,但是我们没有写一行switch…case语句块,反而是将对象的各个状态抽出来做成状态类,然后各个状态类在对各个行为做出实现,代码更加精简。

状态模式具体的状态类在对状态做出变更时其行为也跟着做出变更,其实代码量减少并不十分明显,但是对于状态拓展十分友好,只需要增加状态类再实现各个行为即可拓展新的状态出来,也体现了开闭原则及单一职责原则;状态模式将对象状态的变更放到类的内部进行,外部调用者无需关心对象的状态及行为的变化,也体现了更好的封装性;另外对代码的cpd ( 代码重复率检测)也是很有提升明显。

总结

策略模式

定义

策略模式(Strategy Pattern: Define a family of algorithms,encapsulate each one,andmake them interchangeable.)

中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费劲,其实就是定义一组通用算法的上层接口,各个算法实现类实现该算法接口,封装模块使用类似于Context的概念,Context暴漏一组接口,Context内部接口委托到抽象算
法层。

类图

包含的角色罗列如下:

  • 上下文角色(Context) :该角色一般是一个实现类或者封装类,起到一定的封装及隔离作用,实际接受请求并将请求委托给实际的算法实现类处理,避免外界对底层策略的直接访问;
  • 抽象策略角色( Strategy) :该角色一般是一一个抽象角色,为接口或者抽象类扮演,定义具体策略角色的公共接口;
  • 具体策略角色( ConcreteStrategy) :实现抽象策略角色的接口,为策略的具体实现类。

优缺点

策略模式的优点如下:

  • 所有策略放入一组抽象策略接口中,方便统一管理与实现;
    策略模式的缺点如下:

  • 策略模式每种策略都是单独类,策略很多时策略实现类也很可观;

  • 客户端初始化Context的时候需要指定策略类,这样就要求客户端要熟悉各个策略,对调用方要求较高。

应用场景

策略模式的应用场景如下:

  • 需要自由切换算法的场景
  • 需要屏蔽算法实现细节的场景

使用实例:鸭子模型

类图:

import "fmt"

type FlyBehavior interface {
	Fly()
}

type QuackBehavior interface {
	Quack()
}

type Duck struct {
	fly FlyBehavior
	quack QuackBehavior
}

func (d *Duck)Swim() {
	fmt.Println("鸭子游泳")
}

func (d *Duck) Display (behavior FlyBehavior,quackBehavior QuackBehavior) {
	behavior.Fly()
	quackBehavior.Quack()
}

type FlyWithWings struct {}

func (f *FlyWithWings) Fly ()  {
	fmt.Println("鸭子用翅膀飞")
}

type FlyNoWay struct {}

func (f *FlyNoWay) Fly ()  {
	fmt.Println("鸭子飞不起来")
}

type Quack struct {}

func (f *Quack) Quack ()  {
	fmt.Println("鸭子嘎嘎叫")
}

type Squeak struct {}

func (f *Squeak) Quack ()  {
	fmt.Println("鸭子咔咔叫")
}

type Mute struct {}

func (f *Mute) Quack ()  {
	fmt.Println("鸭子不能叫")
}

type ReadHead struct {
	*Duck
	fly *FlyWithWings
	quack *Quack
}

func (r *ReadHead) Display ()  {
	r.Swim()
	r.Duck.Display(r.fly, r.quack)
}

type Wooden struct {
	*Duck
	fly *FlyNoWay
	quack *Mute
}

func (r *Wooden) Display ()  {
	r.Swim()
	r.Duck.Display(r.fly,r.quack)
}

type Mallard struct {
	*Duck
	fly *FlyWithWings
	quack *Quack
}

func (m *Mallard) Display ()  {
	m.Swim()
	m.Duck.Display(m.fly, m.quack)
}

type Rubber struct {
	*Duck
	fly *FlyNoWay
	quack *Squeak
}

func (r *Rubber) Display ()  {
	r.Swim()
	r.Duck.Display(r.fly, r.quack)
}
flynoway := &FlyNoWay{}
	flayWihtwings := &FlyWithWings{}
	quack := &Quack{}
	sqeak := &Squeak{}
	mute := &Mute{}
	duck := ReadHead{
		Duck:  &Duck{},
		fly:   flayWihtwings,
		quack: quack,
	}
	duck.Display()
	mallard := Mallard {
		Duck:  &Duck{},
		fly:   flayWihtwings,
		quack: quack,
	}
	mallard.Display()
	rub := Rubber {
		Duck:  &Duck{},
		fly:   flynoway,
		quack: sqeak,
	}
	rub.Display()
	wooden := Wooden{
		Duck:  &Duck{},
		fly:   flynoway,
		quack: mute,
	}
	wooden.Display()
鸭子游泳
鸭子用翅膀飞
鸭子嘎嘎叫
鸭子游泳
鸭子用翅膀飞
鸭子嘎嘎叫
鸭子游泳
鸭子飞不起来
鸭子咔咔叫
鸭子游泳
鸭子飞不起来
鸭子不能叫

观察者模式

定义

观察者模式( Observer Pattern)也称发布阅模式。

观察者模式的英文定义如下:

Define a one-to-many dependency between objects so that when one objectchanges state, all its dependents are notified and updated automatically.

意思是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

以生活中的例子来说,就像我们订阅报纸一样,每天有多少人订阅,当有新报纸发布的时候,就会有多少人收到新发布的报纸,这种模式就是订阅一发布模式,而报社和订阅者就满足定义中说的,一对多的依赖关系。

类图

观察者模式包含如下角色:

  • 抽象主题(Subject) 角色:该角色又称为“发布者”或”被观察者,可以增加和删除观察者对象;

  • 具体主题( Concrete Subject) 角色:该角色又称为“具体发布者”或“具体被观察者”,它将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过(关联了观察关系)的观察者发出通知;

  • 抽象观察者(Observer) 角色:该角色又称为“订阅者”,定义一个接收通知的接口,在得到主题的通知时更新自己;

  • 具体观察者( Concrete Observer)角色:该角色又称为“ 具体订阅者”,它会实现-个接收通知的方法,用来使自身的状态与主题的状态相协调。

优缺点

观察者模式的优点:

  • 观察者和被观察者之间,实现了抽象耦合。被观察者角色所知道的只是- 个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展;

  • 此模式为广播模式,所有的观察者只需要订阅相应的主题,就能收到此主题下的所有广播。

观察者模式的缺点:

  • 观察者只知道被观察者会发生变化,但不知道何时会发生变化;
  • 如果主题之间有循环依赖,会导致系统崩溃,所以在使用时要特别注意此种情况;
  • 如果有很多个观察者,则每个通知会比较耗时。

应用场景

使用观察模式的典型应用场景如下:

  • 关联行为的场景,例如,在一个系统中,如果用户完善了个人资料,就会增加积分、添加日志、开放一些功能权限等,就比较适合用观察者模式;
  • 消息队列,例如,需要隔离发布者和订阅者,需要处理一对多关系的时候。

使用实例

以生活中的读者订阅为例,假设,读者A和读者B订阅了某平台的图书,当有新的图书发布时就会给两位读者发送图书,实现代码如下。

import "fmt"

// 读者接口(订阅接口)
type IReader interface {
	Update(bookName string)
}

// 读者类(订阅者)
type Reader struct {
	name string
}

func (r *Reader) Update(bookName string) {
	fmt.Println(r.name,"-收到了图书",bookName)
}

// 平台接口(发布方接口)
type IPlatform interface {
	Attach(reader IReader)
	Detach(reader IReader)
	NotifyObservers(bookName string)
}

// 具体发布类(发布方)
type Platform struct {
	list []IReader
}

func (p *Platform) Attach(reader IReader) {
	// 增加读者(订阅者)
	p.list = append(p.list, reader)
}

func (p *Platform) Detach(reader IReader) {
	// 删除读者(订阅者)
	for i,v := range p.list {
		if v == reader {
			// 删除第i个元素,因为interface类型在golang中
			// 以地址的方式传递,所以可以直接比较进行删除
			// golang中只要记得byte,int,bool,string,数组,结构体,默认传值,其他的默认传地址即可
			p.list = append(p.list[:i],p.list[i+1:]...)
		}
	}
}

func (p *Platform) NotifyObservers(bookName string) {
	// 通知所有读者
	for _,reader := range p.list {
		reader.Update(bookName)
	}
}

func (p *Platform) Change (bookName string)  {
	p.NotifyObservers(bookName)
}


    // 创建图书平台(发布者)
	platform := Platform{list: []IReader{}}
	// 创建读者A
	reader := Reader{name:"A"}
	// 读者A订阅图书通知
	platform.Attach(&reader)
	// 创建读者B
	reader2 := Reader{name:"B"}
	// 读者B订阅图书通知
	platform.Attach(&reader2)
	platform.Change("《go核心编程》")
	// 读者B取消订阅
	platform.Detach(&reader2)
	platform.Change("《go高级编程》")
A -收到了图书 《go核心编程》
B -收到了图书 《go核心编程》
A -收到了图书 《go高级编程》

解释器模式

定义

解释器模式( Interpreter Pattern )提供了评估语言的语法或者表达式的方式,属于一种行为型的设计模式。
解释器模式的英文原话是:

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

意思是:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。简单来说,就是我们可以定义一种语法比如就是一个表达式如: a-b+c, 起初我们并不知道这个句子想要携带信息或者执行什么操作,然后我们要定义一个解析器来进行表达式解析,以便得到正确的结果。

对于表达式: a-b+c 这种,我们做个简单的分析, a、b、c这种我们又叫做运算参数,+、-符号这种我们称之为运算符号,也就说这类表达式我们可以将其抽象为两种角色:运算参数、运算符号。运算参数一般就是英文字母,执行时各个参数需要赋上具体的数字值去替代英文字母执行,运算参数有一个共同点就是不管是a、b或者其它参数,除了被赋值之外不需要做其它任何处理,是执行时的最小单元,在解释器模式中被称为终结符号。运算符号是进行运算时具体要被解释器解释执行的部分,想象一下,加入我们计算机不知道如何处理类似+、-这种符号,我们是不要针对每一个符号写一个解释方法,以便告诉计算机该符号需要进行何种操作,这也就是解释器模式的核心——需要完成逻辑的解释执行操作,而运算符号在解释器模式中也被称为非终结符号。

类图

通常包含如下角色:

  • 抽象解释器( AbstractExpression) :抽象解释器是一个上层抽象类,用来抽取定义公共的解释方法: interpreter, 具体的解释任务交给子类去完成; .
  • 终结符表达式( TerminalExpression) :是抽象解释器的子类,实现了与文法中的元素相关的解释操作。一般模式中只会有一个终结符表达式也就是终结符的类,但是会有多个实例,比如: a、b、c,这些终结符号可以任意多种但是只有一个类来描述;
  • 非终结符表达式( NonTerminalExpression) :也是抽象解释器的子类,用来实现文法中与终结符相关的操作。该角色一般会有多个实现类,比如+、-运算符号就各自对应一种类实现,分别对应加法解释类和减法解释类,非终结符表达式的类的个数一般会有很多 ,因为我们可执行的操作一般会有很多,这也从侧面加剧了该模式下类设计的复杂性;
  • 上下文(Context) :上下文一般用来定义各个解释器需要的数据或公共功能,比如上面的表达式,我们使用上下文来保存各个参数的值,-般是- 个HashMap对象,以便后面所有解释器都可以使用该上下文来获取参数值;

优缺点

解释器模式的优点:

  • 拓展性强:修改文法规则只需要修改相应的非终结符表达式就可以了,即增加非终结符类就可以了。
    解释器模式的缺点:
  • 采用递归调用方法,不利于调试,增加了系统的复杂性以及降低了系统执行的效率;
  • 解释器模式比较容易造成类设计的膨胀,主要是非终结符表达式类会随着系统的复杂性而膨胀;
  • 可利用的场景比较少;
  • 对于比较复杂的文法不好解析。

应用场景

  • 一个简单语法需要解释的场景,如: sql语法分析,用来解析那种比较标准的字符集;
  • 重复发生的问题可以使用解释器模式,如:日志分析,日志分析时基础数据是相同的类似于我们的终结符,但是日志格式往往是各异的,类似于非终结符,只需要指定具体的实现类即可。

使用实例

现在我们以一个最简单的例子: a+b,我们要做的就是解释执行这段语法文本,a和b是两个字母也叫做两个变量,我们需要使用一个“+”符号来将这俩变量连接起来,假设我们的语言并不知道符号"+"是什么作用,具体作用需要我们去实现(假设我们并不知道+其实是加法的意思),示例比较简单,只是为了说明解释器模式没别的意思。

import "bytes"

type Context struct {
	text string
}

//抽象解释器
type AbstractExpress interface {
	Interpreter(*Context) int
}

// 终结符,即我们的参数构造类
type TerminalExpression struct {
	arg int
}

func (t *TerminalExpression) Interpreter(ctx *Context) int {
	return t.arg
}

// 非终结符,即我们的运算符构造类
type NonTerminalExpression struct {
	left AbstractExpress
	right AbstractExpress
}

func (n NonTerminalExpression) Interpreter(ctx *Context) int {
	// 实现具体的a+b的解释执行操作
	if !bytes.Equal([]byte(ctx.text),[]byte("")) {
		return n.left.Interpreter(ctx) + n.right.Interpreter(ctx)
	}
	return 0
}
import "fmt"
var (
		left AbstractExpress
		right AbstractExpress
		callExpression AbstractExpress
	)
	left = &TerminalExpression{arg:12}
	right = &TerminalExpression{arg:34}
	callExpression = &NonTerminalExpression{left:left,right:right}

	context := &Context{text:"+"}

	result := callExpression.Interpreter(context)
	fmt.Println(result)
46

总结

责任链模式

定义

什么是责任链模式?生活中我们经常遇到这样的问题,比如请假审批需要层层上报处理、遇到问题各个部门甩赖扯皮,像这种,在事情没有被处理之前,会经过一系列阶段,类似于“踢皮球”似的。同样地,当一个请求到达时,在程序无法直接决定由哪个对象负责处理时,客户的请求就会形成一种链式传递,在链上的各个处理对象如果无法直接决定是否由其处理时,就会将请求再传递至下一个链对象,直到请求被处理或者被丢弃等等。这种处理链我们形象称其为“责任链”

责任链模式的定义是:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

类图

责任链模式的核心就是Handler链抽象对象,该对象包含一个指向下一个链对象的私有属性,“链”是责任链的核心,就是使用该属性进行链式调用实现的。责任链模式的包含的角色如下:

  • 请求者( Client) : Client 角色就是向链发送请求的角色;
  • 责任人的抽象类角色( Handler) : Handler角色是模式的核心,Handler知道下一个责任人是谁,并根据责任人的处理能力选择是否将请求转发至下一个责任人。
  • 具体责任人对象( ConcreteHandler) :该角色是具体处理请求的对象.

使用实例

举个例子,初级、中级、高级开发工程师分别处理问题的能力不同,我们假设初级工程师只能处理难度级别为1的问题,中级工程师能处理难度为1、2的问题,高级工程师能处理难度级别为1、2、3的问题,另外我们有一个Request请求代表要处理的请求,内部包含一个难度级别和要请求的内容,我们先来看下类图的设计:

import "fmt"

const (
	DIFFICULTY_LEVEL_1 = 1
	DIFFICULTY_LEVEL_2 = 2
	DIFFICULTY_LEVEL_3 = 3
)

type HandleMessage func(request IRequest)


type IRequest interface {
	// 请求级别
	GetRequestLevel() int
	// 获取要请求的内容
	GetRequest() string
}

type Request struct {
	// 难度1--初级工程师
	// 难度2--中级工程师
	// 难度3--高级工程师
	level int
	request string
}

func InitRequset(level int, request string) *Request {
	r := &Request{
		level:   level,
		request: request,
	}
	switch r.level {
	case 1:
		r.request = "难度级别1的请求是:"+ request
	case 2:
		r.request = "难度级别2的请求是:"+ request
	case 3:
		r.request = "难度级别3的请求是:"+ request
	}
	return r
}

func (r Request) GetRequestLevel() int {
	return r.level
}

func (r Request) GetRequest() string {
	return r.request
}

type Handler interface {
	HandleMessage(request IRequest)
	SetNextHandler(handler Handler)
	Response(request IRequest)
	GetLevel()int
	GetNext() Handler
}

// 初级工程师
type Primary struct {
	level int
	request string
	next Handler
}

func (p *Primary) GetNext() Handler {
	return p.next
}

func (p *Primary) GetLevel() int {
	return p.level
}

func (p *Primary) HandleMessage(request IRequest) {
	message := func(request IRequest)  {
		// 如果请求级别小于可以处理的级别就直接处理
		if request.GetRequestLevel() <= p.GetLevel() {
			p.Response(request)
		} else {
			if p.GetNext() != nil {
				p.next.HandleMessage(request)
			} else {
				fmt.Println("---难度级别为",request.GetRequestLevel(),"的请求无法处理")
			}
		}
	}
	message(request)
}

func (p *Primary) SetNextHandler(handler Handler) {
	p.next = handler
}

func (p *Primary) Response(request IRequest) {
	fmt.Println("---难度级别1的请求---")
	fmt.Printf(request.GetRequest())
	fmt.Println("初级工程师已经处理完毕")
}

func InitPrimary() Handler {
	return &Primary{
		level:   DIFFICULTY_LEVEL_1,
		request: "",
	}
}

type Middle struct {
	level int
	request string
	next Handler
}

func (p *Middle) HandleMessage(request IRequest) {
	message := func(request IRequest)  {
		// 如果请求级别小于可以处理的级别就直接处理
		if request.GetRequestLevel() <= p.GetLevel() {
			p.Response(request)
		} else {
			if p.GetNext() != nil {
				p.next.HandleMessage(request)
			} else {
				fmt.Println("---难度级别为",request.GetRequestLevel(),"的请求无法处理")
			}
		}
	}
	message(request)
}

func (p *Middle) SetNextHandler(handler Handler) {
	p.next = handler
}

func (p *Middle) Response(request IRequest) {
	fmt.Println("---难度级别2的请求---")
	fmt.Printf(request.GetRequest())
	fmt.Println("中级工程师已经处理完毕")
}

func (p *Middle) GetLevel() int {
	return p.level
}

func (p *Middle) GetNext() Handler {
	return p.next
}

type Senior struct {
	level int
	request string
	next Handler
}

func (p *Senior) HandleMessage(request IRequest) {
	message := func(request IRequest)  {
		// 如果请求级别小于可以处理的级别就直接处理
		if request.GetRequestLevel() <= p.GetLevel() {
			p.Response(request)
		} else {
			if p.GetNext() != nil {
				p.next.HandleMessage(request)
			} else {
				fmt.Println("---难度级别为",request.GetRequestLevel(),"的请求无法处理")
			}
		}
	}
	message(request)
}

func (p *Senior) SetNextHandler(handler Handler) {
	p.next = handler
}

func (p *Senior) Response(request IRequest) {
	fmt.Println("---难度级别3的请求---")
	fmt.Printf(request.GetRequest())
	fmt.Println("高级工程师已经处理完毕")
}

func (p *Senior) GetLevel() int {
	return p.level
}

func (p *Senior) GetNext() Handler {
	return p.next
}

var (
		pri Handler
		mid Handler
		sen Handler
		list []IRequest
	)
	list = make([]IRequest,0)
	list = append(list,&Request{
		level:   DIFFICULTY_LEVEL_1,
		request: "1+1=?",
	})
	list = append(list,&Request{
		level:   DIFFICULTY_LEVEL_2,
		request: "4*3",
	})
	list = append(list,&Request{
		level:   DIFFICULTY_LEVEL_3,
		request: "99*99",
	})
	list = append(list,&Request{
		level:   4,
		request: "aaaaaaaaaaa",
	})
	pri = InitPrimary()
	mid = &Middle{
		level:   DIFFICULTY_LEVEL_2,
		request: "",
		next:    nil,
	}
	sen = &Senior{
		level:   DIFFICULTY_LEVEL_3,
		request: "",
		next:    nil,
	}

	// 设置链的顺序
	pri.SetNextHandler(mid)
	mid.SetNextHandler(sen)
	for _,v := range list {
		// 责任链中处理该请求
		pri.HandleMessage(v)
	}
=== RUN   TestInitPrimary
---难度级别1的请求---
1+1=?初级工程师已经处理完毕
---难度级别2的请求---
4*3中级工程师已经处理完毕
---难度级别3的请求---
99*99高级工程师已经处理完毕
---难度级别为 4 的请求无法处理
--- PASS: TestInitPrimary (0.00s)
PASS

迭代器模式

定义

迭代器模式的英文定义如下:

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

意思是:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。迭代器是为容器服务的,容器是指用来容纳其他对象的对象,例如,Collection集合类型、Set类等。

类图

迭代器模式有以下4个角色:

  • 抽象迭代器(Iterator) 角色:该角色负责定义访问和遍历元素的接口;
  • 具体迭代器( Concrete Iterator) 角色:该角色实现Iterator接口,完成容器元素的遍历;
  • 抽象聚集(Aggregate) 角色:该角色提供创建迭代器角色的接口;
  • 具体聚集(Concrete Aggregate) 角色:该角色实现抽象聚集接口,创建出容纳迭代器的对象。

优缺点

迭代器模式的优点:

  • 迭代器模式将数据存储和数据遍历的职责进行分离;
  • 迭代器模式简化了遍历容器元素的操作;
  • 迭代器模式使不同的容器,具备一个统一的遍历接口;
  • 迭代器模式封装了遍历算法,使算法独立于聚集角色,调用者无须知道聚集对象的类型,即使聚集对象的类型发生变化,也不会影响遍历过程。
    迭代器模式的缺点:
  • 由于迭代器模式将数据存储和数据遍历的职责进行分离,如果增加新的聚合类,同时需要增加与之相对,应的迭代器类,这使得类的个数会成对增加,在某种程度上来说增加了系统的复杂性。

应用场景

迭代器的应用很广泛,已经发展成为程序开发中最基础的工具类了。在Java语言中,从JDK1.2开始,增加了java.til.lterator 接口,并将Iterator 应用到各个聚集类( Collection)中,如ArrayList、 Vector、Stack、HashSet 等集合类都实现iterator() 方法,返回一个迭代器Iterator,用于对集合中的元素进行遍历。这使我们在项目中无须在独立地写迭代器,直接使用即可,这样既轻松又便捷。
注意:要尽可能地使用编程语言自身提供的迭代器,而非自己写的迭代器。

// 抽象迭代器
type Iterator interface {
	Next() interface{}
	HasNext() bool
}

// 具体迭代器
type ConcreteIterator struct {
	index int
	size int
	con Aggregate
}

func (c *ConcreteIterator) HasNext() bool {
	return c.index < c.size
}

func (c *ConcreteIterator) Next() interface{} {
	if c.HasNext() {
		res := c.con.GetElement(c.index)
		c.index++
		return res
	}
	return nil
}



// 抽象聚集
type Aggregate interface {
	Add(obj interface{})
	CreateIterator() Iterator
	GetElement(index int) interface{}
	Size() int
}

// 具体聚集
type ConcreteAggregate struct {
	//私有存储容器
	docker []interface{}
}

func (c *ConcreteAggregate) Size() int {
	return len(c.docker)
}

func (c *ConcreteAggregate) Add(obj interface{}) {
	c.docker = append(c.docker,obj)
}

func (c *ConcreteAggregate) CreateIterator() Iterator {
	return &ConcreteIterator{
		index: 0,
		size:  c.Size(),
		con:   c,
	}
}

func (c *ConcreteAggregate) GetElement(index int) interface{} {
	return c.docker[index]
}


	// 定义聚族对象
	var (
		aggregate Aggregate
		iter Iterator
	)
	aggregate = &ConcreteAggregate{docker: []interface{}{}}
	aggregate.Add("java")
	aggregate.Add("Golang")
	aggregate.Add("Python")
	// 遍历
	iter = aggregate.CreateIterator()
	for iter.HasNext() {
		fmt.Println(iter.Next())
	}
=== RUN   TestConcreteAggregate_Add
java
Golang
Python
--- PASS: TestConcreteAggregate_Add (0.00s)
PASS

如果您觉得本篇文章不错,请记得到我的GitHub点上一个star,您的支持是我最大的动力!十分感谢!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
评论

打赏作者

liuyonglun

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值