- 前言
- 模块方法 (Template Method)
- 策略模式 (Strategy)
- 观察者模式 (Observer)
- 装饰器 (Decorator)
- 桥模式 (Bridge)
之前面试游戏各大厂跟游戏新贵的时候,总是被问到“设计模式”相关内容,但我由于在本科和研究生期间并没有学过这个东西,唯一的了解也就是在腾讯实习的时候,组内开发的游戏使用了MVC架构。
痛定思痛,提交了毕设论文盲审之后,打算了解一下“设计模式”是什么,所以本文打算记录一下b站《C++设计模式》的学习经历。
我自己对于“设计模式”的理解是,对于“面向对象语言如何设计整个项目的框架”这个问题,程序员先辈们进行很多尝试并且总结出了很多经验,他们对代码架构的优秀经验就是“设计模式”,设计模式对游戏各个模块的设计是很重要的。
优秀的项目需要有合理的架构,比如当你完成一个C++图像处理库的所有功能之后,你需要为以后可能添加的功能做好设想,当以后添加另一个功能时,需要改变这个项目的多少部分,是否需要重新部署,这也就是设计模式需要考虑的东西。
Cpp库里面要实现一个固定流程的方法,这个方法包含五个部分,Step1~Step5
,其中某些步骤是固定的,例如Step1、Step3、Step5
,另一些步骤例如Step2
和Step4
则是由用户自己定义的。
由于这个方法步骤固定,也就是Step1~Step5
肯定依次执行,所以可以在Library的Run
函数中定义这个方法,在Library内部实现Step1、Step3、Step5
,并将Step2
和Step4
定义为纯虚函数,开放给用户定义,这个Library的Run
方法依然是封闭的,模块方法拥有着更好的封装性。
#include
using namespace std;
//程序库开发人员
class Library {
public:
//稳定 template method
void Run() {
Step1();
if (Step2()) {
//支持变化 ==> 虚函数的多态调用
Step3();
}
for (int i = 0; i < 4; i++) {
Step4(); //支持变化 ==> 虚函数的多态调用
}
Step5();
}
virtual ~Library() {}
protected:
void Step1() {
//稳定
cout << "Step1" << endl;
}
void Step3() {
//稳定
cout << "Step3" << endl;
}
void Step5() {
//稳定
cout << "Step5" << endl;
}
virtual bool Step2() = 0; //变化
virtual void Step4() = 0; //变化
};
#include "Library.h"
#include
using namespace std;
//应用程序开发人员
class Application : public Library {
protected:
virtual bool Step2() {
//... 子类重写实现
cout << "override Step2" << endl;
return true;
}
virtual void Step4() {
//... 子类重写实现
cout << "override Step4" << endl;
}
};
int main() {
Library *pLib = new Application();
pLib->Run();
delete pLib;
}
策略模式 (Strategy)
作者在策略模式讲解过程中举了一个各国货币汇率换算问题,如果在Calculate
函数中使用if else
来枚举,当增加另外一个国家时,需要修改Calculate
函数,显得很不优雅,可以使用多态来设计这个代码。
TaxStrategy
是一个基类,定义了纯虚函数Calculate
负责计算汇率,需要让各个国家派生类来实现。
对于每个国家,定义一个类继承TaxStrategy
,并且实现Calculate
函数。
在各国家类xxTax
的创建中,使用工厂方法,策略模式拥有着更好的拓展性,并且能够节约计算资源。
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val = strategy->Calculate(context);
//...
}
};
观察者模式 (Observer)
模式定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
作者举了一个文件分割器的例子来讲解观察者模式,MainForm
代表一个文件分割器界面,可以输入文件路径和文件分割个数。
此时我们需要给MainForm
添加若干个进度显示器,此时使用抽象通知机制而不是具体通知控件,如果MainForm
选择传具体通知控件给FileSplitter
,则FileSplitter
依赖于MainForm
。
一种更好的方式就是定义一个抽象的IProgress
代表抽象通知机制,MainForm
继承FileSplitter
和IProgress
接口,并实现IProgress
中显示进度的方法。
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress() {}
};
class FileSplitter {
string m_filePath;
int m_fileNumber;
List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber) {}
void split(){
// 1.读取大文件
// 2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//发送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() ) {
(*itor)->DoProgress(value); //更新进度条
itor++;
}
}
};
class MainForm : public Form, public IProgress {
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this);
splitter.addIProgress(&cn);
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
装饰器 (Decorator)
动态(组合)地给一个对象增加一些额外的职责。
就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。
作者在装饰器设计模式中举了文件系统作为例子,在一般的设计中,FileSream、NetworkStream、MemoryStream
继承Stream
,在下一层中,CryptoFileStream、BufferedFileStream、CryptoBufferedFileStream
分别继承FileStream
,这就出现了一个问题。
前者可以认为是继承,后者则更像组合而不是继承。
我们可以用后图所示的结构实现这个系统,左边继承右边组合。
这就很优雅,很大程度上避免了代码的重复。
解决了原先仅靠继承方法的“子类急剧膨胀,充斥着 大量重复代码”的问题,使得原本的编译时装配升格为运行时装配。
//业务 *** 作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//写文件流
}
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//读网络流
}
virtual void Seek(int position){
//定位网络流
}
virtual void Write(char data){
//写网络流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//读内存流
}
virtual void Seek(int position){
//定位内存流
}
virtual void Write(char data){
//写内存流
}
};
//扩展 *** 作
// 由于两个子类有相同的成员Stream*,所以这个成员要往上提
DecoratorStream: public Stream {
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm) { }
};
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm) { }
virtual char Read(int number){
//额外的加密 *** 作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密 *** 作...
stream::Seek(position);//定位文件流
//额外的加密 *** 作...
}
virtual void Write(byte data){
//额外的加密 *** 作...
stream::Write(data);//写文件流
//额外的加密 *** 作...
}
};
class BufferedStream : public DecoratorStream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):DecoratorStream(stm) { }
//...
};
void Process() {
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
桥模式 (Bridge)
由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
作者在桥模式中举了同时拥有业务和平台两个维度扩展的例子,作者在重构过程中将Messager
拆成了Messager
和MessagerImp
两个基类,因为作者认为有关业务跟平台的函数放在一个类中不利于扩展,此时Messager
中有MessagerImp
成员,属于组合。
对于业务的扩展,作者令MessagerLite
和MessagerPerfect
分别继承Messager
;对于平台的扩展,作者令PCMessagerImp
和MobileMessagerImp
分别继承MessagerImp
。
在运行时,将两个维度装配。
实现了代码的复用,实现了运行时装配。
class Messager{
protected:
MessagerImp* messagerImp;//...
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual ~Messager(){}
};
// 不同的变化方向(业务和平台),所以分为两个类
class MessagerImp{
public:
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual MessagerImp(){}
};
//平台实现 n
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//==========
}
virtual void DrawShape(){
//==========
}
virtual void WriteText(){
//==========
}
virtual void Connect(){
//==========
}
};
//业务抽象 m
//类的数目:1+n+m
class MessagerLite :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->DrawShape();
//........
}
};
class MessagerPerfect :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->PlaySound();
//********
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->PlaySound();
//********
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->PlaySound();
//********
messagerImp->DrawShape();
//........
}
};
void Process(){
//运行时装配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new Messager(mImp);
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)