c++ 设计模式 的课本范例(中)

(10)单例模式 singleton 。整个应用程序执行时,只有一个单例模式的对象。

class GameConfig    // 懒汉式,啥时候用单例对象,啥时候创建。
{
private:
	static GameConfig* ptrGameConfig;  // 这些函数都作为私有函数,不让外界调用
	GameConfig() {}
	~GameConfig() {}
	GameConfig(const GameConfig&) {}
	GameConfig& operator= (const GameConfig&) {}
public:
	static GameConfig* getGameConfig() 
	{
		if (ptrGameConfig)  // 本代码非线程安全,需要在  main 主线程中,在创建子线程之前,创建单例模式的对象
			ptrGameConfig = new GameConfig();

		return ptrGameConfig;
	}
};
GameConfig* GameConfig::ptrGameConfig = nullptr;

int main()
{
	auto ptr = GameConfig::getGameConfig();

	return 0;
}

下面介绍饿汉式的单例对象生成:

class GameConfig   // 饿汉式,在程序启动时候,在 main 函数执行前,就完成对 单例对象的初始化与创建
{
private:
	static GameConfig* ptrGameConfig;  // 这些函数都作为私有函数,不让外界调用
	GameConfig() {}
	~GameConfig() {}
	GameConfig(const GameConfig&) {}
	GameConfig& operator= (const GameConfig&) {}
public:
	static GameConfig* getGameConfig() { return ptrGameConfig;	}
};
GameConfig* GameConfig::ptrGameConfig = new GameConfig();  // 此时可以调用私有的单例模式的构造函数

int main()
{
	auto ptr = GameConfig::getGameConfig();

	return 0;
}

若有其它静态全局变量的初始化引用了单例对象的数据,可能会导致错误。因为各个 cpp 文件的全局静态变量的初始化的顺序是不太确定的。

以上的单例模式返回的都是单例对象的指针。可以改为返回单例对象的引用,用引用比指针更符合 c++ 的风格,且可以少起变量名:

class GameConfig  
{
private:
	GameConfig() {}    // 这些函数都作为私有函数,不让外界调用
	~GameConfig() { }
	GameConfig(const GameConfig&) {}
	GameConfig& operator= (const GameConfig&) {}
public:
	static GameConfig& getGameConfig() 
	{ 
		static GameConfig gameConfig;
		return gameConfig;
	}
};


int main()
{
	auto& gameConfig = GameConfig::getGameConfig();   // 直接使用 auto 是值复制, auto & 才是创建引用。

	return 0;
}

用 MFC 框架测试不显式析构单例模式的对象,是否会造成内存泄露。发现不会。因为 这些 static 静态对象是在 main 函数之前就构建出来的,静态对象的析构与回收由 main 函数结束后由系统回收。也就不存在内存泄露。 MFC 框架测试的内存泄露应该指的是在 main 函数结束前,申请的内存没有显式释放,才叫内存泄露,贴图如下,无论使用的是静态单例模式对象的指针还是引用,都没提示内存泄露:

在这里插入图片描述

(11)外观模式 fasade 。用于隔离类与类,代码与代码之间的强耦合。比如设置游戏环境:图形、声音、特效。设置会很繁琐,细致。这就是代码里的强耦合。可以在游戏界面里提供两个按钮:高配模式与低配模式。由这两个模式自行进行所有的游戏环境设置。这高配低配按钮的实现,就是外观模式。词典翻译是 fasade 正面;(尤指虚假的)外表,表面,外观。个人感觉表面模式更合适。表面,区别于内核,由表面联接与内核打交道。

class Graphic
{
private:
	Graphic() {}
	~Graphic() {}
	Graphic(const Graphic&) {}
	Graphic& operator=(const Graphic&) {}
public:
	static Graphic&  getGraphic()
	{
		static Graphic gra;
		return gra;
	}

	void effects_mode(bool b)  // 是否开启特效
	{
		if (b) cout << "开启高特效\n\n";
		else   cout << "不开启高特效\n\n";
	}

	void resolution(int i , int j)	{	cout << "分辨率 :" << i << " * " << j << "\n\n";	}
};

class Sound
{
private:
	Sound() {}
	~Sound() {}
	Sound(const Sound&) {}
	Sound& operator=(const Sound&) {}
public:
	static Sound&  getSound()
	{
		static Sound sound;
		return sound;
	}

	void bgSound(bool b)     // 是否开启背景音
	{
		if (b) cout << "开启背景音\n\n";
		else   cout << "不开启背景音\n\n";
	}

	void envirSound(bool b)  // 是否开启环境音效
	{
		if (b) cout << "开启环境音效\n\n";
		else   cout << "不开启环境音效\n\n";
	}
};

class Facade // 外观类,用于隔离类之间的强耦合
{
private:
	Facade() {}
	~Facade() {}
	Facade(const Facade&) {}
	Facade& operator=(const Facade&) {}
public:
	static Facade& getFacade()
	{
		static Facade facade;
		return facade;
	}

	void config(bool high)
	{
		auto& graphic = Graphic::getGraphic();
		auto& sound = Sound::getSound();

		if (high)
		{
			graphic.effects_mode(true);
			graphic.resolution(1920 , 1080);

			sound.bgSound(true);
			sound.envirSound(true);
		}
		else
		{
			graphic.effects_mode(false);
			graphic.resolution(1366, 800);

			sound.bgSound(false);
			sound.envirSound(false);
		}
	}

};

int main()
{
	auto& fasade = Facade::getFacade();
	fasade.config(true);
	cout << "----------------------\n\n";
	fasade.config(false);
	return 0;
}

测试结果如下:

在这里插入图片描述

(12)命令模式 Command 。例如 wps 软件的菜单,由一个个命令组成。把这些执行了的命令,集合到容器里,如 list 双向链表。就可以编写日志,或者支持了撤销操作, undo 。

class Cook
{
public:
	void fish() { cout << "厨师做了鱼\n\n"; }
	void beef() { cout << "厨师做了牛肉\n\n"; }
};

class Command    // 封装命令
{
protected: Cook* pCook;
public:
	Command( Cook* p) :pCook(p) {}
	virtual ~Command() {}
	virtual void excute() = 0;
};

class Command_Fish : public Command  // 做鱼命令
{
public:
	Command_Fish( Cook* p) : Command(p) {}
	void excute() override { this->pCook->fish(); }
};

class Command_Beef : public Command   // 做牛肉命令
{
public:
	Command_Beef(Cook* p) : Command(p) {}
	void excute() override { this->pCook->beef(); }
};

class Waiter              // 用服务员角色一次可以执行很多命令
{
private: list<Command*> listCommand;
public:
	void addCommand(Command* p)    { listCommand.push_back(p); }
	void deleteCommand(Command* p) { listCommand.remove(p); }
	
	void excute() 
	{
		for (auto iter = listCommand.begin(); iter != listCommand.end(); iter++)
			(*iter)->excute();
	}
};

int main()
{
	Cook cook;
	Command_Fish cmdFish(&cook);
	Command_Beef cmdBeef(&cook);

	Waiter waiter;
	waiter.addCommand(&cmdFish);
	waiter.addCommand(&cmdBeef);

	waiter.excute();

	return 0;
}

测试结果如下:

在这里插入图片描述

(13)迭代器模式 itertor 。主要适用于对容器的操作,定义了迭代器模式,以对容器进行增删改查的操作,同时要良好的组织这些数据结构,以实现高效。目前迭代器模式用的不多了,因为 STL 标准库定义了各种容器及其迭代器。库大师们的顶尖的容器设计,我们直接用就可以了。

for ( auto iter = vector . begin() ; iter != vector . end() ; iter++ )
	auto temp = * iter ;

以上代码显示了定义一个迭代器时应具有的最小功能: begin() 函数返回容器的头部, end() 函数返回容器的尾部 , 迭代器要支持 自加
的运算, * 运算符返回迭代器指向的容器中的元素。从而由这些函数配合完成对容器的遍历操作。即使以后要咱们自己写迭代器,也应该模仿库大师们的代码比如用模板方式定义容器及其迭代器。

(14) 组合模式 compasite 。 对于文件系统,一个目录可以包含文件与目录,子目录里又可以包含新的文件与目录。为了描述和组织这种树型的数据结构,编码的方式叫做组合模式,其实更确切的叫法应该叫树型模式。

先给出第一版代码与测试结果:

class File
{
private:
	string fileName;
public:
	File(const string& s) : fileName(s) {}

	void showName(const string& indentStr) { cout << indentStr << "-" << fileName << '\n'; }  // indent : 缩进
};

class Directory
{
private:
	string dirName;
	list<File*> listFile;
	list<Directory*> listDirectory;
public:
	Directory(const string& s) : dirName(s) {}

	void addFile(File* f) { listFile.push_back(f); }
	void addDir(Directory* d) { listDirectory.push_back(d); }

	void showName(const string& indentStr)
	{
		cout << indentStr << '+' << dirName << '\n';
	
		string newIndentStr = indentStr + "    ";

		for (auto iter = listFile.begin(); iter != listFile.end(); iter++)
			(*iter)->showName(newIndentStr);

		for (auto iter = listDirectory.begin(); iter != listDirectory.end(); iter++)
			(*iter)->showName(newIndentStr);

	}
};

int main()
{
	File h("h"), i("i") , e("e") , f("f") , b("b") , c("c");
	Directory g("G") , d("D") , a("A");

	g.addFile(&h);
	g.addFile(&i);

	d.addFile(&e);
	d.addFile(&f);

	a.addFile(&b);
	a.addFile(&c);
	a.addDir(&d);
	a.addDir(&g);

	a.showName("");

	return 0;
}

测试结果如下,即为图中的目录进行了编码:

在这里插入图片描述

以上版本把 文件和目录作为了两种类型。其实可以把其视为一种类型,用类的继承与多态来实现,由此得到组合模式的第二版(例子同上):

class FileSystem // 把文件类型与目录类型视为同一种类型
{
public:
	virtual ~FileSystem() {}
	virtual void showName(int indentNum) = 0;
	virtual int addFile(FileSystem* f) = 0;  // 返回值 0 表示正确给目录增删了文件, 返回 -1 表不应当对文件类型进行目录的增删操作
	virtual int addDir(FileSystem* d) = 0;
};

class File : public FileSystem
{
private:
	string fileName;
public:
	File(const string& s) : fileName(s) {}

	virtual int addFile(FileSystem* f) { return -1; }
	virtual int addDir(FileSystem* d) { return -1; }

	void showName(int indentNum)   // indent : 缩进
	{
		for (int i = 0; i < indentNum; i++)
			cout << "    ";

		cout << "-" << fileName << '\n';
	}
};

class Directory : public FileSystem
{
private:
	string dirName;
	list<FileSystem*> listFS;
public:
	Directory(const string& s) : dirName(s) {}

	int addFile(FileSystem* f) { listFS.push_back(f); return 0; }
	int addDir(FileSystem* d) { listFS.push_back(d); return 0; }

	void showName(int indentNum) override
	{
		for (int i = 0; i < indentNum; i++)
			cout << "    ";
	
		cout << "+" << dirName << '\n';

		indentNum++;

		for (auto iter = listFS.begin(); iter != listFS.end(); iter++)
			(*iter)->showName(indentNum);
	}
};

int main()
{
	File h("h"), i("i") , e("e") , f("f") , b("b") , c("c");
	Directory g("G") , d("D") , a("A");

	g.addFile(&h);
	g.addFile(&i);

	d.addFile(&e);
	d.addFile(&f);

	a.addFile(&b);
	a.addFile(&c);
	a.addDir(&d);
	a.addDir(&g);

	a.showName(0);

	return 0;
}

(15) 状态模式 state。用以解决以下场景:在编译原理中,根据一门语言的语法,编写出有限状态机。对代码的编译过程,实际始终是在该有限状态机的几个状态上跳转。这几个状态,足以满足用该门语法编写的所有代码情形。或者游戏里,打怪物。怪物受伤后的状态,始终就那几种,有章可循。给出课本代码,写在一个页面了,为了简洁,突出整体。没有拆分成头文件与 cpp 文件。

class Monster;

class Status   // 状态的基类
{
public:
	virtual ~Status() {}
	virtual void attacked(int power, Monster* ptrMonstor) = 0;
};

class Status_Violent : public Status  // 出于编译原理的从上往下编译,只能把包含大量符号的函数体下移。否则总提示符号找不到
{
public:
	virtual void attacked(int power, Monster* ptrMonstor);

	static Status* getStatusObj();
};

class Monster
{
private:
	int life;
	Status* pStatus;  // 这里原先使用了左值引用,但不好使,总因为 是否具有 const 属性报错,改成课本上的指针了。
public:
	Monster() : life(500), pStatus(Status_Violent::getStatusObj()) {}  // 新生怪物处于  violent 状态,满血
	int getLife() { return life; }
	void setLife(int t) { life = t; }
	Status* getStatus() { return pStatus; }
	void setStatus(Status* s) { pStatus = s; }
	void attacked(int power) { pStatus->attacked(power, this); }
};


class Status_Dead : public Status
{
public:
	virtual void attacked(int power, Monster* ptrMonstor) {	}

	static Status* getStatusObj()
	{
		static Status_Dead objDead;
		return &objDead;
	}
};

class Status_Fear : public Status
{
public:
	virtual void attacked(int power, Monster* ptrMonstor)
	{
		cout << " 怪物处于 Fear 状态 , " << " 但受到了 " << power << "  点攻击  , ";

		int newLife = ptrMonstor->getLife() - power;

		if (newLife >= 1)
			cout << "  仍处于 fear 状态\n\n";
		else
		{
			cout << "  进入了 Dead 状态\n\n";
			ptrMonstor->setStatus(Status_Dead::getStatusObj());
		}

		ptrMonstor->setLife(newLife);
	}

	static Status* getStatusObj()
	{
		static Status_Fear objFear;
		return &objFear;
	}
};

void Status_Violent::attacked(int power, Monster* ptrMonstor)  // 这个函数包含的符号最多,只能放在最后,要不总提示符号找不到
{
	cout << " 怪物处于 violent 状态 , " << " 但受到了 " << power << "  点攻击  , ";

	int newLife = ptrMonstor->getLife() - power;

	if (newLife >= 300)
		cout << "  仍处于 violent 状态\n\n";
	else if (newLife >= 1)
	{
		cout << "  进入了 fear 状态\n\n";
		ptrMonstor->setStatus(Status_Fear::getStatusObj());
	}
	else
	{
		cout << "  进入了 Dead 状态\n\n";
		ptrMonstor->setStatus(Status_Dead::getStatusObj());
	}

	ptrMonstor->setLife(newLife);
}

Status*  Status_Violent:: getStatusObj()
{
	static Status_Violent objViolent;
	return &objViolent;
}

int main()
{
	Monster m;
	m.attacked(100);
	m.attacked(150);
	m.attacked(50);
	m.attacked(400);
	return 0;
}

测试结果如下:

在这里插入图片描述

(16)享元模式 Flyweight 。也叫蝇量模式。比如围棋游戏,要绘制棋子。只要知道棋子的颜色和坐标信息,就可以绘制出该棋子。但也会造成创建大量的重复的小对象–棋子。由此提出享元模式。让程序中代码共享共用一些对象,达到减少内存使用提高效率的效果。比如可以只在围棋环境中创建黑子白子两个对象。依据位置,重复在不同坐标处绘制。以下给出范例代码:

enum Color{ black , white };

struct Position
{
	int x;
	int y;
};

class Piece
{
public:
	virtual ~Piece() {}
	virtual void draw( const Position& p) = 0;
};

class Piece_White : public Piece
{
public:
	void draw(const Position& p) override
	{
		cout << "  在棋盘 ( " << p.x << " , " << p.y << " ) 处,画了一个白棋子\n\n";
	}
};

class Piece_Black : public Piece
{
public:
	void draw(const Position& p) override
	{
		cout << "  在棋盘 ( " << p.x << " , " << p.y << " ) 处,画了一个黑棋子\n\n";
	}
};

class Factory
{
private:
	map< Color, Piece* > mapPiece;
public:
	~Factory()
	{
		for (auto iter = mapPiece.begin(); iter != mapPiece.end(); iter++)
			//delete iter->second;  这两种写法是等价的
			delete (*iter).second;
	}

	Piece* getPiece(Color color)
	{
		auto iter = mapPiece.find(color);
		
		if (iter != mapPiece.end())
			return iter->second;
		else
		{
			Piece* ptr = nullptr; 
			
			if (color == Color::black)
				ptr = new Piece_Black;
			else
				ptr = new Piece_White;

			mapPiece.insert(make_pair(color , ptr));

			return ptr;
		}
	}
};

int main()
{
	Factory fact;

	fact.getPiece(black)->draw(Position(3, 4));
	fact.getPiece(white)->draw(Position(6, 9));

	return 0;
}

测试结果如下:

在这里插入图片描述

(17)代理模式 Proxy 。 比如把要访问的网站,当成一个对象。把对网站对象的直接操作(比如访问网站的行为 ) 改成对网站代理的访问,进而实现其它的附加控制:比如流量控制,密码控制等。代码如下:

class web
{
public:
	virtual ~web() {}  // 作为基类,析构函数一定要定义成虚函数。
	virtual void visit() = 0;
};

class Shopping : public web
{
public:
	void visit() { cout << " 访问了购物网站\n\n"; }
};

class Proxy : public web
{
private: web* ptrWeb;
public:
	Proxy(web* w) :ptrWeb(w) {}
	void visit() { ptrWeb->visit(); }
};


int main()
{
	Shopping siteShop;
	Proxy proxy(&siteShop);
	proxy.visit();

	return 0;
}

再给出一例,访问磁盘文件时的缓存代理,及其测试结果:

#include<fstream>
#include<string>
vector<string> vecString;  // 这个全局量就是读取文件内容时的缓冲代理

class ReadInfo
{
public:
	virtual ~ReadInfo() {}
	virtual void read() = 0;
};

class ReadInfoFromFile : public ReadInfo
{
public:
	void read()
	{
		ifstream fin("test.txt");  // 包含头文件 <fstream>
		
		if (!fin)
		{
			cout << "文件打开失败\n\n";
			return;
		}
	
		string buf;
		while (getline(fin , buf))   // 使用 全局 getline () 函数,需要包含头文件 <string>
			if (!buf.empty())
				vecString.push_back(buf);
		
		fin.close();
	}
};

class Proxy : public ReadInfo
{
private: bool readed = false;  // 显示代理中是否有数据
public:
	void read()
	{
		if (readed)
			cout << " 没有从文件读取,从缓存代理读取了:\n\n";
		else
		{
			readed = true;
			cout << " 从文件读取了数据:\n\n";
			
			ReadInfoFromFile r;
			r.read();
		}

		for (auto iter = vecString.begin(); iter != vecString.end(); iter++)
			cout << *iter << endl;
	}
};


int main()
{
	Proxy proxy;
	proxy.read();
	proxy.read();

	return 0;
}

测试结果如下:

在这里插入图片描述

(18) 适配器模式 Adapter ,也叫包装器 Wrapper 。比如手机充电器就是适配器。把本来不能用一个类的地方,现在可以用了,就是因为适配器的包装与代码改造。范例代码如下:

class LogToDatabase
{
public:
	virtual ~LogToDatabase() {}
	virtual void initDB() { cout << "初始化日志环境(数据库)\n\n"; }
	virtual void writeToDB() { cout << "写入日志(数据库)\n\n"; }
	virtual void readFromDB() { cout << "读出日志(数据库)\n\n"; }
	virtual void closeDB() { cout << "关闭日志环境(数据库)\n\n"; }
};

class LogToFile
{
public:
	void initFile() { cout << "初始化日志环境(文件)\n\n"; }
	void writeToFile() { cout << "写入日志(文件)\n\n"; }
	void readFromFile() { cout << "读出日志(文件)\n\n"; }
	void closeFile() { cout << "关闭日志环境(文件)\n\n"; }
};

class Adapter : public LogToDatabase
{
private: LogToFile* ptr;
public :
	Adapter(LogToFile* p) : ptr(p) {}
	virtual void initDB() { ptr->initFile(); }
	virtual void writeToDB() { ptr->writeToFile(); }
	virtual void readFromDB() { ptr->readFromFile(); }
	virtual void closeDB() { ptr->closeFile(); }
};

int main()
{
	LogToDatabase data;
	data.initDB(); data.writeToDB(); data.readFromDB(); data.closeDB();

	cout << "--------------------------------------\n\n";

	LogToFile file;
	Adapter adapter(&file);
	adapter.initDB(); adapter.writeToDB(); adapter.readFromDB(); adapter.closeDB();

	system("pause");
	return 0;
}

测试结果如下:

在这里插入图片描述

谢谢

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值