当前位置: 首页 > article >正文

设计模式(4)行为模式

行为模式

      • 1. Chain of Responsibility Pattern(责任链模式)
      • 2.Command Pattern(命令模式)
      • 3.Interpreter Pattern(解释器模式)▲
      • 4.Iterator(迭代器模式)
      • 5.Mediator(中介者模式)
      • 6. Memento(备忘录模式)
      • 7.Observer Pattern(观察者模式)
      • 8. State Pattern(状态模式)
      • 9. Strategy Pattern(策略模式)
      • 10.Template Method Pattern(模板方法模式)
      • 11.Visitor Pattern(访问者模式)
      • 模式之间关系

1. Chain of Responsibility Pattern(责任链模式)

模式定义
责任链模式允许将请求沿着处理者链进行传递,直到有一个处理者能够处理该请求。每个处理者都包含对下一个处理者的引用,形成一个链条。

模式构成

  • Handler(处理者):定义一个处理请求的接口,通常包含一个指向下一个处理者的引用。处理者可以决定自己是否处理请求,或者将请求传递给下一个处理者。
  • ConcreteHandler(具体处理者):实现处理请求的具体处理逻辑。如果自己能够处理请求,则处理请求;否则将请求传递给下一个处理者。
  • Client(客户端):创建请求对象并将其传递给责任链的第一个处理者,从而启动责任链的处理过程。
    在这里插入图片描述

模式特点

  • 降低耦合度:请求发送者和接收者之间解耦,每个处理者只需关注自己的责任范围。
  • 灵活性:可以动态地改变责任链的组成,增加或删除处理者,或者改变处理者之间的顺序。
  • 可扩展性:易于新增处理者,扩展新的业务逻辑,不需要修改已有代码。

常见的应用场景包括

  • 有多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  • 在不明确指定接受者的情况下,向多个对象中的一个提交一个请求。
  • 可处理一个请求的对象集合应能被动态指定。

抽象场景
考虑一个图形用户界面中的窗口上下文有关的帮助机制,用户在界面的任意部分点击就可以获取到帮助信息,所提供的帮助信息依赖于点击的是界面的那一部分以及其上下文。下面是一个界面以及不同窗口上下文的嵌套关系:
在这里插入图片描述
实现上,每个在链上的对象都要有一致的处理请求和访问链上后继者的接口,因此,可以定义一个带有HandleHelp操作的HelpHandler类作为基类来处理链式请求。例如,按钮、对话框和应用类都使用HelpHandler类来处理获取帮助的请求(HelpHandler作为他们的基类),HelpHandler的HandlerHelp操作缺省是将请求转发给后继者,子类可重定义这一操作以在条件符合的情况下提供帮助,否则可使用缺省实现转发该请求。
在这里插入图片描述

示例伪代码

//定义一个处理者类
class HelpHandler {
public:
    HelpHandler (HelpHandler * successor) : _successor(successor) {} //构造函数中将后继者链接到链上,即给后继者成员变量赋值
    virtual void HandleHelp(const std::string& request)  //默认将请求转发给后继者
    {
        if (_successor) 
        {
            _successor->HandleHelp(request); 
        }
    }
protected:
    HelpHandler* _successor; //后继者
};

//具体的处理者类
class Widget : public HelpHandler
{
public:
	Widget(HandleHelp* h) : HelpHandler(h){} //加后继者
	Widget(){}//不加后继者
	void HandleHelp(const std::string& request)
	{
		if(match(request))
		{
			//do..
		}
		else 
		{
			HelpHandler::HandleHelp();
		}
	}
	
}
class Button : public HelpHandler
{
	//与Widget实现一样
}
class Dialog : public HelpHandler
{
	//...
}
//test
Dialog * d = new Dialog();
Button * b = new Button(d);
Widget * w = new Widget(b);
w->HandleHelp("dialog");

2.Command Pattern(命令模式)

模式定义
将请求封装成一个对象,从而允许客户端通过不同的请求参数化对象,并支持请求队列、请求日志记录、撤销请求等功能。这种方式可以将命令的发送者与命令的接收者解耦,从而实现更松散的耦合。

模式构成

  • Command(命令):定义命令的接口,包括执行命令的方法 execute()。
  • ConcreteCommand(具体命令):实现命令接口,封装了命令的具体行为和接收者。
  • Receiver(接收者):知道如何实施与执行一个请求相关的操作。
  • Invoker(调用者):要求命令执行请求的对象,通过命令对象来处理请求。
  • Client(客户端):创建具体命令对象并设置其接收者,然后将命令对象传递给调用者。

模式工作原理
该模式通过命令类来降低发送者和接收者的耦合度。具体的行为有:
(1)客户端需要创建一个具体命令对象,并指定它的接收者对象。
(2)调用者对象保存该具体命令对象,并通过调用命令对象的Execute操作来提交一个请求。
(3)最终,具体命令对象会调用接受者对象的方法来执行该请求。
在这里插入图片描述

模式特点

  • 解耦:发送者和接收者之间解耦,发送者只需知道如何发送命令,而不需要知道如何执行命令。
  • 可扩展性:易于添加新的命令,无需修改已有代码。
  • 支持撤销和重做:可以轻松实现命令的撤销和重做功能(命令类中新增对应接口即可)。

常见的应用场景包括

  • GUI应用程序中,命令模式常用于实现菜单和工具栏按钮的操作。每个菜单项或按钮可以被表示为一个命令对象,从而实现操作的撤销、重做以及动态变化。
  • 文本编辑器中,命令模式可以用于实现撤销、重做、复制、粘贴、剪切等操作,每个操作都可以被封装成一个命令对象。
  • 实现日志系统,每个日志记录操作可以被封装成一个命令对象,方便记录日志并支持撤销。
  • 实现事务系统,一个事务封装了多个命令对象。

抽象场景
用命令模式可以很容易为应用实现菜单,每一个菜单选项配置一个具体的Command子类的实例,当用户选择一个菜单选项时,应用会调用它的Command对象的Execute方法,由Execute执行相应的操作,Execute操作将调用某个接收者的一个或多个操作。
例如,一个粘贴的菜单项,配置一个PasteCommand类的实例,它的接收者是一个文档对象,Execute操作将调用文档对象的Paste操作。对应的打开、关闭、复制等操作都同理。
在这里插入图片描述
示例伪代码

class Command
{
public:
	virtual void Execute() = 0;
}

class OpenCommand : public Command
{
public:
	OpenCommand(string str, Document *doc) : _str(str), _doc(doc){}
	void Execute()
	{
		if(_doc)	_doc->open(_str);
	}
private:
	Document *_doc;
	string _str;
}
class PasteCommand : public Command
{...}

class Application 
{
public:
	void setOpenCommand(Command *c){_command = c;} //设置命令
	void openMenu()//执行打开菜单
	{
		if(_command) _command->Execute();
	}
...
private:
	Command *_command;
}
//test open menu
Document *doc = new Document(); //接收者
OpenCommand *open = new OpenCommand("file", doc); //具体命令
Application *app = new Application();
app->setOpenCommand(open);
app->openMenu();

3.Interpreter Pattern(解释器模式)▲

模式定义
解释器模式用于定义一个语言的文法,并定义一个解释器,该解释器使用文法解释语言中的句子。它为解决特定类型的问题提供了一种灵活的解决方案,可以用于解释和执行特定语言或规则。

模式构成

  • 抽象表达式(Abstract Expression):定义解释器的接口,包括一个 interpret() 方法,用于解释语言中的表达式。这个接口为抽象语法树中所有节点所共享。
  • 终结符表达式(Terminal Expression):实现抽象表达式中的 interpret() 方法,用于解释语言中的终结符,可以理解为一条规则产生的最终结果。
  • 非终结符表达式(Non-terminal Expression):通常是复合表达式,包含多个子表达式,每个子表达式可以是终结符表达式或其他非终结符表达式。文法中每条规则都需要一个非终结符表达式。
  • 环境类(Context):包含解释器解释的全局信息或状态,通常用于传递表达式中的信息。
    在这里插入图片描述

模式工作原理

  • 解释器模式通过定义一种语言的语法表示,并解释语法表示的句子,将句子翻译成实际操作。
  • 客户端创建并配置特定的表达式对象,形成一个表示特定语言的表达式树。
  • 解释器按照表达式树的结构,逐步解释表达式。从根节点开始,递归地解释每个子表达式。

模式特点

  • 灵活,可以轻松地扩展语言或规则,而不必修改现有代码。
  • 可维护,将语法规则表达为类结构,使得维护和理解代码更加容易。
  • 解释器模式在处理复杂的语法规则时非常有用,但对于简单的情况可能会增加代码复杂性。

常见的应用场景包括

  • SQL解释器
  • 编程语言解释器
  • 正则表达式引擎

抽象场景
实现一个布尔表达式求值器。布尔表达式拥有的运算符有:and、or、not,其规则定义如下:
boolExp = orExp | andExp | notExp | constant | varExp;
orExp = boolExp or boolExp ;
andExp = boolExp and boolExp ;
notExp = not boolExp ;
varExp = a-z | 0-9 | A-Z…;
constant = true | false;
其中constant为规则中的终结符表达式,即布尔常量true和false;其它几种均为非终结符表达式。

(1== 2)or(2 ==2)and 3 and(true)

示例伪代码

//抽象表达式boolExp 
class BooleanExp
{
public:
	virtual bool interpret() = 0;
}
//非终结符表达式varExp 
class VariableExp: public BooleanExp
{
public:
	VariableExp(char* var):_var(var){}
	bool interpret(){ return context(_var) ;} //context是用来解释_var是真是假的函数,即它会返回一个终结符表达式true或者false
	char* _var;
}
//非终结符表达式andExp  
class AndExp: public BooleanExp
{
public:
	AndExp(BooleanExp *b1, BooleanExp *b2):_b1(b1), _b2(b2){}
	bool interpret(){ return _b1.interpret() && _b2.interpret();}
	BooleanExp *_b1;
	BooleanExp *_b2;
}
class OrExp : BooleanExp{...} //同AndExp相似
class NotExp : BooleanExp{...}//同AndExp相似


//test : (true or'x' ) and (y and or'x')
VariableExp vx = new VariableExp('x');
VariableExp vy = new VariableExp('y');

BooleanExp *exp = new AndExp(new OrExp(vx, constant(true)) , new OrExp(vy, new NotExp(vx)));
exp->interpret();

4.Iterator(迭代器模式)

模式定义
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。即通过迭代器模式,可以在不暴露集合底层数据结构的情况下访问集合中的元素。

模式构成

  • 迭代器(Iterator):定义了访问和遍历元素的接口,包括获取下一个元素、判断是否还有元素等方法。
  • 具体迭代器(Concrete Iterator):实现迭代器接口,在具体集合上进行遍历和访问元素。
  • 聚合对象(Aggregate):定义创建迭代器的接口,通常包含一个返回迭代器的方法。
  • 具体聚合对象(Concrete Aggregate):实现创建迭代器接口,返回具体的迭代器。

模式特点

  • 分离集合对象的遍历行为:迭代器模式将集合对象和遍历行为分离,使得可以独立地改变集合的遍历方式。
  • 简化集合接口:通过迭代器模式,集合类可以专注于自身的数据结构和操作,而将遍历操作交给迭代器来实现。
  • 支持多种遍历方式:迭代器模式可以针对同一个集合对象实现多种不同的遍历方式,提高灵活性。

抽象场景
实现一个简单List(列表)类的迭代器。

示例伪代码

//具体聚合对象(以标准库中的list为例)
template <class Item>
class List 
{
public:
	List(long size){...}
	int size(){...}
	Item &Get(long index) const {...}
	Iterator *CreateIterator(){...}
	//...
private:
	//...
};
//迭代器
template <class Item>
class Iterator
{
public : 
	virtual void first() = 0;
	virtual void next() = 0;
	virtual void end() = 0;
	virtual Item CurrentItem() const = 0;
	//....
};
//具体迭代器
template <class Item>
class ListIterator
{
public:
	ListIterator(const List<Item>* list):_list(list), _current(0) {}
	void first() { _current = 0 ;} 
	void next() { _current++ ;}
	bool end() { if(_current >= _list->size() ) return true; return false;} 
	Item CurrentItem() const { if(!end()) _list->Get(_current) ;}
private:
	const List<Item>* _list;
	long _current;
}

//test 假设有一个Node类的集合需要被遍历
List<Node> li;
ListIterator *it = li.CreateIterator();
for(it.first(); !it.end(); it.next)
{
	do(it.CurrentItem());
}
delete it;
//代码中没有完整体现到模式构造图的各部分,完整的体现应该是给List一个抽象的父类class AbstractList,这样如果有一个SkipList类型的集合,也可以继承该类,以体现多态的原则。

5.Mediator(中介者模式)

模式定义
中介者模式通过一个中介对象来封装一系列对象交互。这样,对象之间就不需要显式地引用彼此,从而使其耦合度降低,同时也更容易维护和扩展系统。

模式构成

  • 中介者(Mediator):定义了一个接口用于与各个同事对象通信。
  • 具体中介者(Concrete Mediator):实现了中介者接口,协调各个同事对象的行为。
  • 同事对象(Colleague):每个同事对象都知道中介者对象,与其他同事通过中介者进行通信。
  • 具体同事对象(Concrete Colleague):实现各自的行为,需要与其他同事对象协同工作时,通过中介者进行通信。
    在这里插入图片描述

模式特点

  • 减少耦合:对象之间通过中介者通信,相互之间不直接引用,降低了对象之间的耦合度。
  • 集中控制:中介者可以集中控制对象之间的交互行为,更容易管理和维护系统。
  • 易扩展:增加新的同事对象或者改变同事对象的交互行为时,只需要修改中介者即可,不影响其他对象。

常见的应用场景包括

  • 一组对象以复杂的方式进行通信,产生相互依赖关系混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用。
  • 想定制一个分布在多个类中的行为,而又不想生成太多子类。

抽象场景
在这里插入图片描述
实现上述对话框。对话框中各组件存在依赖关系,例如,当输入栏为空时,下面的OK按钮组件不能使用;输入文本匹配列表会匹配输入栏中输入的文本并展示一些相近的可供选择的文本条目;一旦输入栏有内容,调整文本格式的按钮组件就可以使用等等。

不同的对话框会有不同的窗口组件间的依赖关系。因此即使对话框显示相同类型的窗口组件,也不能简单的直接重用已有的窗口组件类型,而必须定制它们以反映特定对话框的依赖关系。由于涉及很多类,用逐个生成子类的方式来定制它们就会显得很冗余。此时,就可以通过将集体行为封装在一个单独的中介者对象中以避免这个问题。

结构如下:
在这里插入图片描述

示例伪代码

//定义一个中介者类
class DialogDirector
{
public:
	virtual void ShowDialog();
	virtual void WidgetChanged(Widget*) = 0;
	virtual void CreateWidget() = 0;
};
//定义一个抽象同事类
class Widget
{
public:
	virtual void Changed()
	{_derector->WidgetChange(this);}
	virtual void HandleMouset(MousetEvent& event);
	//...
private:
	DialogDirector* _derector;
}
//具体的同事类(通过MousetEvent驱动)
//输入文本匹配列表
class ListBox : public Widget
{
public: 
	const char *GetSelection(){}//获取某一条文本
	void SetList(List<char*>* listItems){} //设置展示的文本列表
	void HandleMouset(MousetEvent& event){changed();}//鼠标事件,例如双击选择某条文本
	//...
}
//输入栏
class EntryField : public Widget
{
public:
	void SetText(const char *text){} //设置文本内容
	const char *GetText(){}//获取文本内容
	void HandleMouset(MousetEvent& event){changed();}//鼠标事件
	//...
}
//按钮
class Button : public Widget
{
public:
	void HandleMouset(MousetEvent& event){changed();} //鼠标点击后通知其他组件改动
	//...
}
//具体的中介类
class FontDialogDirector : public DialogDirecotr
{
public:
	 void CreateWidget() //创建需要展示的组件,这里也可以将各个组件以参数的形式传进来,这里为了简单直接函数中创建
	 {
	 	_ok = new Button(this); _cancel = new Button(this);
	 	_List = new ListBox(this);
	 	_Field = new EntryField(this);
	 	//...
	 }
	 void WidgetChanged(Widget* w) //核心,协调各组件之间的行为
	 {
	 	if(w == _List){ _field->SetTxt(_List->GetSelection());}
	 	else if(w == _ok) {...}
	 	//...
	 }
private:
	Button* _ok, *_cancel;
	ListBox* _List;
	EntryField* _Field;
}

//test 创建显示组件即可
FontDialogDirector  d = new FontDialogDirector();
d->CreateWidget();
sleep();//等待鼠标或键盘等事件

6. Memento(备忘录模式)

模式定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。后续可以将该对象恢复到原先保存的状态。这种模式通常用于需要实现撤销操作或者保存对象历史状态的场景。

模式构成

  • Originator(原发器):负责创建一个备忘录对象,用于记录当前时刻的内部状态,并可以使用备忘录对象恢复其状态。
  • Memento(备忘录):存储Originator对象的内部状态。除原发器以外的对象都无法访问备忘录,只能将其递送给其他对象。
  • Caretaker(管理者):负责保存备忘录,但不对备忘录的内容进行操作。
    在这里插入图片描述

模式工作原理
在这里插入图片描述
管理器向原发器请求一个备忘录,保存在自己内部,在需要的时候将其送回给原发器。

模式特点

  • 封装性,原发器对象的状态被封装在备忘录中,保证了数据的隐私性。
  • 简化原发器,如果将原发器状态保存在自身内部,那么所有存储管理的重任都集中在原发器,使其过于复杂。
  • 使用备忘录的代价可能会很高,如果原生器在生成备忘录时必须拷贝并存储大量信息时,这种模式可能并不适合。

值得注意的是,如果在原发器中通过接口让其他对象直接得到其状态,将会暴露原发器对象的实现细节,破坏其封装性。

常见的应用场景包括
备忘录模式在需要保存和恢复对象状态的情况下非常有用,例如文本编辑器的撤销功能、游戏中的进度保存等。

抽象场景
这个模式容易理解,没给出实际的例子,在示例代码给出了大致的框架。

示例伪代码

//Originator
class Originator
{
public:
	Memento* Creatememento(){return new Memento().setState(_state);}
	void setMemento(const Memento* m){_state = m->getState();}
	//other interface...
private:
	state* _state;
};

// Memento
class Memento
{
private:
	friend class Originator;  //因为备忘录是私有接口,将原发器设置为友元类,可以访问或更新状态。
	state *getState(){return _state;}
	void setState(state *s){ _state = s;}
	state* _state;
};

// Caretaker
class Caretaker 
{
public:
	Caretaker(Originator *og):_og(og){}
	void execute() //执行某个操作时,原发器保存自己的状态到备忘录
	{
		_memento = _og->Creatememento();
		//do somethings...
	}
	void unexecute() //撤销操作时,原发器从备忘录回滚状态
	{
		_og->setMemento(_memento);
		//do somethings...
	}
private:
	Memento* _memento;
	Originator *_og;
};

// test
Originator* o = new Originator();
Caretaker *c = new Caretaker(o);
c->execute();
//....
c->unexecute();

7.Observer Pattern(观察者模式)

模式定义
观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

模式构成

  • Subject(目标):被观察的对象,它维护一组观察者,提供方法用于添加和删除观察者以及通知观察者的状态变化。
  • Observer(观察者):定义一个更新接口,当接收到目标状态变化的通知时进行相应的更新。
  • ConcreteSubject(具体目标):实现Subject接口,具体目标内部状态发生变化时通知观察者。
  • ConcreteObserver(具体观察者):实现Observer接口,注册到具体目标中,当具体主题状态发生变化时接收通知并进行相应的更新。
    在这里插入图片描述
    模式工作原理
    目标和观察者之间的交互也称为发布-订阅。目标是通知的发布者,它发出通知时并不需要知道谁是观察者(广播)。可以有任意数目的观察者订阅并接受通知。

模式特点

  • 解耦:目标和观察者之间解耦,目标不需要知道观察者的细节,只需知道观察者实现了更新接口。
  • 扩展性:可以轻松添加新的观察者,不会影响到已有观察者。
  • 实时性:观察者能够实时获得目标的状态变化,实现了对象之间的实时通信。

常见的应用场景包括
观察者模式常用于系统中一对多依赖关系的场景,例如GUI组件中的事件处理、消息队列系统中的消息订阅与推送等。

这种依赖关系可以描述为:当对一个对象的改变需要同时改变其他对象,而且还可能不知道具体有多少对象有待改变。

抽象场景
现有一个图形界面工具需要将应用数据以不同的形式展示出来,例如表格展示、柱状图展示等。表格对象和柱状图对象互相并不知道对方的存在,可以根据需要单独复用这两个对象。同时当用户修改表格对象时,柱状图也能立即反应这一变化。

这就是一个比较典型的应用观察者模式的例子。应用数据是目标,不同的图形展示对象就是观察者。当应用数据改变时,展示图形也做对应的调整变化。
在这里插入图片描述

示例伪代码

//目标基类
class Subject
{
public:
	void Attach(Observer*o){ _arr_o.push_back(o);}
	void Detach(Observer*o){ _arr_o.erase(o);};
	void Notify()
	{  
		for o in _arr_o 
			o.Update();
	};
private:
	vector<Observer*> _arr_o;
};
//观察者基类
class Observer
{
public:
	void Update();
private:
	Subject* _sub;
};
//应用数据类
class DataSubject : public Subject
{
public:
	DataSubject(data* d):_data(*d){}
	void Notify()
	{
		if(_date.change())
		{
			Observer::Notify();
		}
	}
	void SetData(data & d) { _data = d;}
	data GetData() { return _data; }
private:
	data _data; //需要展示的数据,data是一个数据集合控制类 
}
//表格展示类
class TableDisplayObserver : public Observer
{
public:
	void Update()  //实现展示具体数据的接口,目标发来通知的是同步更新展示
	{
		data data_t;
		data_t = _sub.GetData();
		show(data_t);
	} 
};

//test 
data * da = new data();
DataSubject  *sub = new DataSubject(da);
TableDisplayObserver *ob = new TableDisplayObserver();
sub->Attach(ob);
if(da->dataChange())
{
	sub->Notify();
}
//这里没有给出一个观察者类影响另一个观察者类变化的代码,大致的思路就是在观察者中实现一个方法可以调用sub->SetData() + sub->Notify();

8. State Pattern(状态模式)

模式定义
状态模式允许对象在内部状态发生改变时改变它的行为。它将对象的行为封装在不同的状态对象中,对象在不同状态下表现不同的行为。

模式构成

  • Context(上下文):定义客户端感兴趣的接口,维护一个当前状态对象的引用。
  • State(状态):定义一个接口来封装与Context的一个特定状态相关的行为。
  • ConcreteState(具体状态):每一个具体状态类实现了与Context的一个状态相关的行为。
    在这里插入图片描述

模式工作原理
引入一个State的抽象类来表示状态,并为不同的操作状态声明公共的接口。ConcreteState子类实现与特定状态相关的行为。

模式特点

  • 封装性:将状态相关的行为封装在不同的状态类中,使得每个状态类的行为更加清晰。有效避免大量条件语句的存在,当相应也会增加子类的数量。
  • 扩展性:易于增加新的状态类,而不会影响原有状态类的行为。
  • 灵活性:通过改变状态对象,可以改变Context的行为,使得对象在不同状态下表现不同的行为。

常见的应用场景包括
一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。例如状态机、工作流程管理等场景。
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。

抽象场景
考虑到一个网络连接类TCPConnection,可能存在若干不同的状态:建立连接、正在监听、关闭连接。当一个TCPConnection对象收到其他对象的请求时,它能根据自身的状态作出不同的反应。例如一个Open操作。
在这里插入图片描述

示例伪代码

class TCPConnection
{
public:
	void Open() { _state->open(this); }
	void Close() { _state->close(this); }
	//....
private:
	friend calss TCPState; //状态的改变可以放在(1)具体的状态类中去做,也可以通过(2)TCPConnection自己来做。这里将状态类设置为友元就是实现第一种方式
	void changeState(TCPState* s){ _state = s;}
private:
	TCPState* _state;
};

class TCPState
{
public:
	virtual void Open(TCPConnection *t) { }
	virtual void Close(TCPConnection *t) { }
	//....
protected:
	void ChangeState(TCPConnection *t, TcpState *s){ t->ChangeState(s); }
};
class TCPEstablished : public TCPState
{
public:
	static *TCPState* Instance(); //(1)可以将状态设置为单例;(2)或者是在需要使用的时候创建,使用完销毁。两种方式
	virtual void Open(TCPConnection *t) { //do thing ...}
	virtual void Close(TCPConnection *t) 
	{ 
		//do something...
		ChangeState(t, TCPClosed::Instance()); //做完对应的操作后,自动转换状态
	}
};
class TCPClosed : public TCPState{....};
class TCPListen : public TCPState
{
public:
	static *TCPState* Instance();	
	virtual void Open(TCPConnection *t) 
	{ //do thing ...
		ChangeState(t, TCPEstablished::Instance());
	}
};

TCPConnection* t = new TCPConnection();
//假设开始监听,设置状态
t->changeState(TCPListen::Instance());
//假设新的连接到来,需要调用open,其中会自动转换为下一个状态
t->Open();
//业务做完了,需要close,此时是TCPEstablished状态调用close操作
t->close();

9. Strategy Pattern(策略模式)

模式定义
策略模式允许在运行时选择算法的行为,算法被封装在各自的类中,它们可以相互替换。这种模式提供了一种简单的方法来切换不同的算法或策略,而不需要改变使用算法的客户端。

模式构成

  • Context(上下文):维持一个对策略对象的引用,在需要的时候调用策略对象来执行具体的算法。
  • Strategy(策略):定义所有支持的算法的公共接口。所有具体策略类都必须实现此接口。
  • ConcreteStrategy(具体策略):实现策略接口,提供具体的算法实现。

在这里插入图片描述
ps:从结构上来看和状态模式是类似的。差异主要是在行为上。

模式工作原理
Context中一般会持有一个Strategy的引用,通过选择不同的Strategy子类来实现策略的选择。当算法被调用时Context可以将自身作为参数传递给Strategy,方便其相互作用。简单的来说,就是通过子类来消除if…else…的选择方式,提高灵活性,降低耦合度。

模式特点

  • 灵活性:可以在运行时动态地选择算法。
  • 避免条件语句:避免使用大量的条件语句来判断不同的情况。
  • 易于扩展:可以轻松地添加新的策略。
  • 单一职责原则:每个具体策略类都封装了一个算法,符合单一职责原则。
  • 增加了子类对象的数目(不可避免的问题)。

常见的应用场景包括

  • 许多类仅仅是行为有异,但可以通过相同的接口来表示不同行为。
  • 使用一个算法的不同变体。算法的具体数据结构不应该被客户知晓。
  • 一个类定义了多种行为,并且这些行为在代码中体现为多个条件分支。此时可用策略模式消除这种情况。

总的概括,策略模式通常需要在运行时动态选择算法的场景下使用,例如支付系统中选择不同的支付方式,或者排序算法中选择不同的排序策略等。

抽象场景
考虑到一个编辑器,需要对正文内容进行分行。如果说将不同的分行算法硬编码进编辑器类中,可能会造成如下问题:(1)分行算法较多时,编辑器代码更加复杂,臃肿并且难以维护。(2)不同的时刻使用不同的分行算法,此时编辑器并不需要包含所有的分行算法。(3)新增分行算法时或将改变现有逻辑,引发问题。
因此,可以使用策略模式,封装不同的换行算法,从而避免这些问题。
在这里插入图片描述

示例伪代码

class Composition
{
public:
	Composition(Compositor* c, Component * cn):_c(c), _cn(cn){}
	void Repair()
	{
		//...
		_c->Compose(_cn); //修改正文布局,即根据换行算法给出具体如何换行
		//...
	}
private: 
	Compositor* _c;
	Component * _cn; //正文布局信息
};

class Compositor
{
public:
	virtual int Compose(Component * cn);
};
class SimpleCompositor : public Compositor
{
public:
	int Compose(Component * cn){ // do thing ...}
};
class TexCompositor : public Compositor {...}

//test
Component * cn = new Component();
Composition *sc = new Composition(new SimpleCompositor(), cn); //先初始化,需要那种算法就使用哪种类来初始化
Composition *tc = new Composition(new TexCompositor(), cn);
//...
sc->Repair();//执行算法
//...

10.Template Method Pattern(模板方法模式)

模式定义
模板方法模式定义了一个操作中的算法的骨架,并将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的特定步骤。

模式构成

  • AbstractClass(抽象类):定义了一个模板方法,其中包含算法的骨架,以及一系列抽象方法来代表算法的各个步骤。
  • ConcreteClass(具体类):实现了抽象类中定义的抽象方法,完成算法中的具体步骤。
    在这里插入图片描述
    模式特点
    模板方法模式,可以实现算法的复用和扩展,同时避免代码重复,提高代码的可维护性和可扩展性。

常见的应用场景包括

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中存在公共的行为可以被提取出来,并集中放到公共父类中避免代码重复。

抽象场景
现有一个提供Application和Document类的应用框架,Application类负责打开一个文档,一旦文档中的信息从该文件中读出后,它就由一个Document对象来表示。
用框架构建的应用可以通过继承Application和Document来满足特定的需求。例如,一个绘图应用定义DrawApplication和DrawDocument子类。
在这里插入图片描述

示例伪代码

//两个AbstractClass
class Document 
{
public:
	virtual void Open() {}
	virtual void Save() {}
	virtual void Close() {}
	virtual void DoRead() = 0;
}
class Application
{
public:
	//将打开一个文档并展示写成固定的算法,具体的实现延迟到子类中实现
	void OpenDocument(char * name)
	{
		if(CanOpenDocument(name)
		{
			_doc = new DoCreateDocument(name);
			if(_doc)
			{
				AddDocument(_doc);
				_doc->Open();
				_doc->DoRead();
			}
		}
	}
	virtual void CanOpenDocument(){}
	virtual void AddDocument(Document *){}
	virtual void DoCreateDocument(char *name ) = 0;
private:
	Document *_doc;
}

//ConcreteClass
class DrawDocument : public Document 
{
public:
	void DoRead() {//do real things....}
	//...
}
class DrawApplication : public Application
{
public:
	void DoCreateDocument(char *name) {//do real things...}
	//...
}

///test
DrawApplication * app = new DrawApplication();
app->OpenDocument("test.png");

11.Visitor Pattern(访问者模式)

注:可以先看抽象场景,通过具体实例了解该模式大致是什么,然后再看模式的定义、构成和特点。

模式定义
访问者模式(Visitor Pattern)是一种将算法与对象结构分离,使得可以在不改变对象结构的前提下,定义作用于这些对象结构上的新操作。这种模式适用于对象结构相对稳定,但经常需要定义新的操作或功能的情况。

模式构成

  • Visitor(访问者):定义了对对象结构中各元素的访问操作,可以为每个具体元素类提供一个访问操作的实现。
  • ConcreteVisitor(具体访问者):实现了 Visitor 定义的操作,为对象结构中的每个具体元素类提供了具体的访问操作。
  • Element(元素):定义一个 accept 操作,该操作传入一个访问者对象作为参数,使访问者对象可以访问元素。
  • ConcreteElement(具体元素):实现了 Element 定义的 accept 操作,通常会调用访问者对象的相应操作。
  • ObjectStructure(对象结构):包含了多个元素,提供一个接口让访问者对象可以访问它的元素。
    在这里插入图片描述

模式工作原理
抽象的Visitor类为每一个ConcreteElement类声明一个VisitConcreteElement操作,并且使用特定的ConcreteElement作为参数,以允许Visitor直接访问ConcreteElement的接口。ConcreteVisitor类重定义每一个Visit操作,从而为相应的ConcreteElement类实现与特定访问者相关的行为。

Visitor模式使用的是一种双分派技术。通俗点说就是Accept请求需要执行的具体操作依赖于Visitor的类型和Element的类型。

模式特点

  • 增加新操作:仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。
  • 相关操作集中:将相关的操作集中到一个访问者类中,使得代码易于维护和扩展。
  • 访问者劣势:增加新元素类会导致访问者类的修改,可能会导致代码比较复杂,不容易理解。
  • 破环封装:该模式常常会迫使提供访问元素的内部状态的操作,从而破环元素对象的封装性。

常见的应用场景包括
应用访问者模式的核心:确认系统的那部分会经常变化,是作用于对象结构上的算法,还是构成该结构的各个对象的类。如果老是有新的ConcreteElement类加入进来,visitor类层次将变得难以维护。这种情况下,直接在构成该结构的类中定义需要操作会更容易一些(例如,抽象场景中的原始方法)。如果Element类层次是稳定的,只是需要不断的增加操作或修改算法,访问者模式则更加合理。

常用场景:编译器的语法树遍历、文档结构分析等。

抽象场景
考虑到一个编译器,它将源程序表示为一个抽象语法树。编译器会对抽象语法树进行静态语义分析。因此它需要定义多种操作以进行类型检查、代码优化、代码生成、流程分析、变量检查等处理。
这些操作大多要求对不同的节点进行不同的处理,例如对赋值语句节点的处理就不同于算术表达式节点的处理。因此我们可以衍生出以下类层次:
在这里插入图片描述
上述的类层次可能会导致一个问题:将所有操作集中放到节点类中会导致整个系统难以理解、维护和修改。例如新增一个操作将需要重新编译所有这些类。
为了解决上述问题,引入Visitor模式,通过将每个操作包装成一个独立的类(Visitor类),将每种节点当作参数传递给Visitor类接受访问

例如,不使用访问者的编译器会在对应的节点直接调用TypeCheck函数做类型检查。
而使用了访问者的编译器会先创建一个TypeCheckingVisitor对象作为访问者,并将节点作为访问者操作接口的参数。然后将访问者设置为节点中Accept函数的参数,在Accept函数中,节点会调用访问者的具体操作接口(同时将自己传递给访问者)。例如一个赋值节点会通过TypeCheckingVisitor对象来调用VisitAssignment操作。

使用访问者前,类AssignmenNode的TypeCheck操作变成了现在TypeCheckingVisitor的VisitAssignment操作。
使用如下类层次结构:
在这里插入图片描述
节点类也使用类层次结构关联起来:
在这里插入图片描述
通过上面类层次结构,我们发现,此时新增一个操作时,只需要新增一个访问者子类即可。
当然如果节点类频繁变更将会导致visitor类不适用,上面的‘常见的应用场景’中有说到。

示例伪代码
示例代码给出访问者模式的简单模板,并非上述的抽象场景,通过简单模板更好理解:

//声明一个访问者基类,定义访问元素A、B的操作接口
class Visitor
{
public:
	virtual void visitElementA(ElementA*);
	virtual void visitElementB(ElementB*);
	//...
}
//具体访问者,定义了对元素A、B多种操作之一,例如检查类型
class VisitorOptOne : public Visitor
{
public:
	virtual void visitElementA(ElementA*){ ... }
	virtual void visitElementB(ElementB*){ ... }
}
//对元素A、B多种操作之二,例如引用变量节点
class VisitorOptTwo : public Visitor
{
public:
	virtual void visitElementA(ElementA*){ ... }
	virtual void visitElementB(ElementB*){ ... }
}
//元素,定义了accept接口,接受访问
class Element
{
public:
	virtual void Accept(Visitor& ) = 0;
}

class ElementA : public Element
{
public:
	virtual void Accept(Visitor& v) {v.visitElementA(this)}
}
class ElementB : public Element
{
public:
	virtual void Accept(Visitor& v) {v.visitElementB(this)}
}
//
class ObjectStructure
{
public:
	void addE(Element* e) { _list.insert(e);}
	void accept(Visitor &v)
	{
		for(auto it : _list)
		{
			it->accept(v);
		}
	}
private:
	list<Element*> _list;
}

//test
ElementA *A = new ElementA(); //元素A,例如赋值节点
ElementA *B = new ElementB(); //元素B,例如引用变量节点
ObjectStructure obj;
obj.addE(A);
obj.addE(B);
VisitorOptOne vone;//第一种操作,例如检查类型
obj.accept(vone);//对两个节点做类型检查
VisitorOptTwo vtwo;//第二种操作,例如优化代码
obj.accept(vtwo);//对两个节点做优化代码

模式之间关系

  1. 责任链模式常与组合模式一起使用。
  2. 解释器模式中的抽象语法树(规则)使用的是组合模式(Composite)来实现的。
  3. 多态迭代器靠工厂模式来例化适当的迭代器子类。
  4. 中介者模式中,同事对象可以通过观察者模式与中介者对象通信。在中介者模式章节中案例使用的是另一种方式:在中介者对象中定义一个特殊的通知接口,当同事对象有变化时,直接调用该接口。
  5. 观察者模式可以和中介者模式结合使用,通过中介者来封装复杂的更新语义。
  6. 策略模式(Strategy)使用委托来改变整个算法,模板方法模式(Template Method)使用继承来改变算法的一部分。

http://www.kler.cn/a/507819.html

相关文章:

  • doc、pdf转markdown
  • 【从零开始使用系列】StyleGAN2:开源图像生成网络——环境搭建与基础使用篇(附大量测试图)
  • 【React】静态组件动态组件
  • 深度学习图像算法中的网络架构:Backbone、Neck 和 Head 详解
  • 基于YOLOv8与CGNet的鸟类智能识别系统 深度学习图像分类 鸟类目标检测与分类 图像特征提取 模型优化与应用 数据可视化(源码+指导+定制)
  • vmware虚拟机配置ubuntu 18.04(20.04)静态IP地址
  • 正则表达式 匹配特定字符后的所有字符
  • SSE 实践:用 Vue 和 Spring Boot 实现实时数据传输
  • GORM(Go语言数据交互库)
  • Java ArrayList
  • Angular 19 新增的一些核心功能
  • 从零开始启动一个Vue项目
  • JavaSE第八天
  • 软件工程师欧以宁:引领无人机导航与物联网安全的技术革新
  • ToDesk设置临时密码和安全密码都可以当做连接密码使用
  • Mimikyu靶场刷题记录
  • 浅谈云计算21 | Docker容器技术
  • 深入探究 Vue 3 中动态组件加载与性能优化
  • git详细使用教程
  • C# Common.Utility
  • C# Sleep() vs Wait():到底用哪个?
  • Swift语言的多线程编程
  • Python编程与在线医疗平台数据挖掘与数据应用交互性研究
  • JavaScript,ES6,模块化,大程序文件拆分成小文件再组合起来
  • vue3学习三
  • 网络安全 | 防护技术与策略