(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;elseptr = 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)
谢谢