(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;
}
(18)
谢谢