(19) 桥接模式 Bridge,不是采用类继承,而是采用类组合,一个类的数据成员是类对象,来扩展类的功能。源码如下:
class OS // 操作系统负责绘图
{
public:
virtual ~OS() {}
virtual void draw(char * ptrCache , int lengthCache) = 0 ;
};
class OS_Window : public OS
{
public:
void draw(char* ptrCache, int lengthCache) { cout << " windows 下绘图\n\n"; }
};
class OS_Linux : public OS
{
public:
void draw(char* ptrCache, int lengthCache) { cout << " Linux 下绘图\n\n"; }
};
class Image // 本基类有成员函数负责解析与加载图片至缓存,但绘图调用跟操作系统有关的底层函数
{
protected: OS * ptrOS;
public:
virtual ~Image() {}
Image(OS* p) : ptrOS(p) {}
virtual void parseImage( const char * name) = 0;
};
class Image_jpg : public Image
{
public:
Image_jpg(OS * p) : Image(p){}
void parseImage(const char* name)
{
cout << " 解析 jpg 图片 , ";
char c[100]; // 假定分析 jpg 格式后的图片信息统一用 100 字节存储。
ptrOS->draw(c , 100);
}
};
class Image_png : public Image
{
public:
Image_png(OS* p) : Image(p) {}
void parseImage( const char* name)
{
cout << " 解析 png 图片 , ";
char c[50]; // 假定分析 png 格式后的图片信息统一用 50 字节存储。
ptrOS->draw(c, 50);
}
};
int main()
{
OS_Window osWindow;
OS_Linux osLinux;
Image_jpg jpgA(&osLinux) , jpgB(&osWindow) ;
Image_png pngA(&osLinux) , pngB(&osWindow) ;
jpgA.parseImage("aaa");
jpgB.parseImage("b");
pngA.parseImage("c");
pngB.parseImage("dd");
return 0;
}
测试结果如下:
(20)中介者模式 Mediator 。比如 UI 设计,页面上的各种控件,牵一发而动全身,彼此联系紧密。若把控件间的联系代码写入各个控件,会很繁琐,庞大。因此可以创建一个中介对象,专门处理当一个 UI 控件的状态变化时的逻辑。也像电脑主板上的总线,借着总线,实现电脑各部件见的连接与通信。以下代码配套的是这个 UI 登录界面:
配套的代码如下:
class Control; // 类的前向声明
class Media // 中介者的基类
{
public:
virtual ~Media() {}
virtual void changed(Control * ptrCtrl) = 0;
};
class Control // 控件的基类
{
protected:
string name; // 控件名称,以此区分控件,所以该名称将是唯一的
Media* ptrMedia; // 显示本控件属于哪个中介管理
public:
virtual ~Control() {}
Control(const string& s, Media* pM) : name(s), ptrMedia(pM) {}
virtual void enable(bool enabled) = 0;
virtual void changed() { ptrMedia->changed(this); } // 控件产生了变化,交由 中介者 来处理这种相关联的变化
};
class Control_Button : public Control
{
public:
Control_Button(const string& s, Media* ptr) : Control(s, ptr) {}
void enable(bool enabled)
{
if (enabled)
cout << " 按钮 " << name << " 被启用\n\n";
else
cout << " 按钮 " << name << " 被禁用\n\n";
}
};
class Control_Radio : public Control
{
public:
Control_Radio(const string& s, Media* ptr) : Control(s, ptr) {}
void enable(bool enabled) {} // 对于单选按钮,不必使用此函数
void select(bool clicked) // 对于单选按钮,增加此函数
{
if(clicked)
cout << " 单选按钮 " << name << " 被启用\n\n";
else
cout << " 单选按钮 " << name << " 被禁用\n\n";
}
};
class Control_text : public Control
{
private : string text; // 文本框里保存了账号或密码的内容
public:
Control_text(const string & t , const string& s, Media* ptr) : text(t) , Control(s, ptr) {}
Control_text(const string& s, Media* ptr) : text(""), Control(s, ptr) {}
void enable(bool enabled)
{
if (enabled)
cout << " 文本框 " << name << " 被启用\n\n";
else
cout << " 文本框 " << name << " 被禁用\n\n";
}
void setText(const string& s) { text = s; } // 对文本框增加此函数
bool isEmpty() { return text.empty(); } // 判断文本框是否为空
};
class Media_UI : public Media
{
private:
Control_Button* pLogin, * pLogout;
Control_Radio* pRadioTourist, * pRadioAccount;
Control_text *pTextName, * pTextPwd;
public:
Media_UI(){}
void init(Control_Button* pLin, Control_Button*pLout, Control_Radio* pTourist, Control_Radio* pAccount, Control_text* pName, Control_text* pPwd)
{
pLogin = pLin; pLogout = pLout;
pRadioTourist = pTourist; pRadioAccount = pAccount;
pTextName = pName; pTextPwd = pPwd;
}
virtual void changed(Control* ptrCtrl)
{
if (ptrCtrl == pLogout) { cout << " 游戏退出!\n\n"; return; }
if (ptrCtrl == pRadioTourist)
{
pRadioTourist->select(true);
pRadioAccount->select(false);
pTextName->enable(false);
pTextPwd->enable(false);
pLogin->enable(true);
return;
}
if (ptrCtrl == pLogin) { cout << " 开始登录验证\n\n"; }
if (ptrCtrl == pRadioAccount)
{
pRadioTourist->select(false);
pRadioAccount->select(true);
pTextName->enable(true);
pTextPwd->enable(true);
if (pTextName->isEmpty() || pTextPwd->isEmpty())
pLogin->enable(false);
else
pLogin->enable(true);
}
if (ptrCtrl == pTextName || ptrCtrl == pTextPwd)
{
if (pTextName->isEmpty() || pTextPwd->isEmpty())
pLogin->enable(false);
else
pLogin->enable(true);
}
}
};
int main()
{
Media_UI media_UI;
Control_Button login("登录", &media_UI), logout("退出" , &media_UI);
Control_Radio tourist("游客登录", &media_UI), account("账户登录" , &media_UI);
Control_text name("昵称", &media_UI), password("密码" , &media_UI);
media_UI.init(&login , &logout , & tourist , & account , & name , & password);
login.changed(); logout.changed();
tourist.changed(); account.changed();
name.changed(); password.changed();
name.setText("aaa"); password.setText("1234556");
login.changed(); account.changed(); name.changed();
return 0;
}
测试结果如下:
(21) 备忘录模式 Memento , 也可以翻译为更简单的 Note 。例如游戏里在某个时刻保存玩家的当时信息。
class Fighter;
class Note // 备忘录模式
{
private:
int life, magic, attack;
friend Fighter; // 本类对 Fighter 类开放所有权限
Note(int life, int m, int a) :life(life), magic(m), attack(a) {} // 私有的构造函数
int getLife() { return life; }
int getMagic() { return magic; }
int getAttack() { return attack; }
void setLife(int t) { life = t; }
void setMagic(int t) { magic = t; }
void setAttack(int t) { attack = t; }
public:
};
class Fighter
{
private:
int life, magic, attack;
public:
Fighter(int life, int m, int a) :life(life), magic(m), attack(a) {}
Note* createNote() { return new Note(life ,magic , attack); } // 此处返回去的指针要记得回收内存,可以用智能指针包装下;
void restoreFromNote(Note* ptr) { life = ptr->life; magic = ptr->magic; attack = ptr->attack; }
void show()
{
cout << " 玩家当前的 life , magic , attack : " << life << " " << magic << " " << attack << '\n';
}
void setStatus() { life /= 2; magic /= 2; attack /= 2; }
};
int main()
{
Fighter f(100,200,300);
unique_ptr<Note> uniNote(f.createNote());
f.show();
f.setStatus();
f.show();
f.restoreFromNote(uniNote.get());
f.show();
return 0;
}
测试结果如下:
( 22) 责任链模式 Chain Of Responsibility 。比如员工的加薪申请,申请的金额越大,就越需要更高层的领导审批。可以把负责审批的管理,创建为一个个对象,并把它们串起来。
class RaiseRequest // 本类的含义是来自某个人的加薪请求
{
private:
string name;
int request;
public:
RaiseRequest(const string& s, int t) : name(s), request(t) {}
int getRequest() const { return request; } // 这里的 const 不能省略,const 修饰的是 this
};
class Manager // 可以审批加薪请求的领导
{
private:
Manager* next = nullptr;
public:
virtual ~Manager() {}
void setChain(Manager* n) { next = n; }
Manager* getNext() { return next; }
virtual void processRequest(const RaiseRequest& raiReq) = 0;
};
class Manager_Low : public Manager
{
public:
virtual void processRequest(const RaiseRequest& raiReq)
{
auto t = raiReq.getRequest();
if (t < 5000)
cout << " 加薪请求 " << t << " 已被低级管理处理\n\n";
else if (getNext())
getNext()->processRequest(raiReq);
else
cout << " 没有合适的管理;来处理此加薪请求\n\n";
}
};
class Manager_High : public Manager
{
public:
virtual void processRequest(const RaiseRequest& raiReq)
{
auto t = raiReq.getRequest();
if ( t >= 5000 && t < 10000)
cout << " 加薪请求 " << t << " 已被高级管理处理\n\n";
else if (getNext())
getNext()->processRequest(raiReq);
else
cout << " 没有合适的管理来处理此 " << t << " 的加薪请求\n\n";
}
};
int main()
{
Manager_Low mL;
Manager_High mH;
mL.setChain(&mH);
RaiseRequest a("zhangsan" , 500) , b("Lisi" , 6000) , c("wangWu" , 13000);
mL.processRequest(a);
mL.processRequest(b);
mL.processRequest(c);
return 0;
}
测试结果如下:
以上的责任链中,只需要有一个链节处理事务即可,叫单纯责任链。但换个场景,比如网络用语过滤,需要进行性 、脏话 、 政治敏感词,多重过滤,就叫非单纯的责任链模式,链中的每个链节都要被调用,处理事务。
class Filter // 过滤器过滤文本,以使文本文明
{
private:
Filter* next = nullptr;
public:
virtual ~Filter() {}
void setChain(Filter* n) { next = n; }
Filter* getNext() { return next; }
virtual void processRequest(string& str) = 0; // 以形参引用带回返回值
};
class Filter_Sexy : public Filter // 过滤性敏感词
{
public:
virtual void processRequest(string& str)
{
cout << " sexy 敏感被处理\n\n";
str += "XXX";
if(getNext())
getNext()->processRequest(str);
}
};
class Filter_Policy : public Filter // 过滤政治敏感词
{
public:
virtual void processRequest(string& str)
{
cout << " policy 敏感被处理\n\n";
str += "ZZZ";
if (getNext())
getNext()->processRequest(str);
}
};
int main()
{
Filter_Sexy fSex;
Filter_Policy fPlcy;
fSex.setChain(&fPlcy);
string s(" 测试文字 ");
fSex.processRequest(s);
cout << " 最终的测试文字: " << s << "\n\n";
return 0;
}
测试结果如下:
(23) 访问者模式 Visitor 。此模式是为了解决如下案例的编码:
某人去看病,大夫列了一个药品单。这些药品就是一个个被访问的对象。这些对象会被不同的人访问。收费处要根据药的价格收费,药房根据药名取药,营养师根据药制定食谱,健身教练根据药制定训练方案。这里引入双重分派机制(double dispatch):谁被访问,又是谁来访问。非常贴合访问者模式。以下给出代码例子与测试结果。
class Visitor;
class Medicine
{
public:
virtual ~Medicine() {}
virtual string getName() = 0;
virtual double getPrice() = 0;
virtual void accept(Visitor* visitor) = 0;
};
class Medicine_Eye : public Medicine
{
public:
virtual string getName() { return " 眼药 "; }
virtual double getPrice() { return 10.5; }
virtual void accept(Visitor* visitor); // 这一步声明不可少,不然报错,而且与上面的 = 0 ,还不一样
};
class Medicine_Nose : public Medicine
{
public:
virtual string getName() { return " 鼻药 "; }
virtual double getPrice() { return 100.5; }
virtual void accept(Visitor* visitor); // 这一步声明不可少
};
class Visitor
{
public:
virtual ~Visitor() {}
virtual void visit_MediEye(Medicine_Eye * mediEye) = 0;
virtual void visit_MediNose(Medicine_Nose * mediNose) = 0;
};
class Visitor_Charge : public Visitor // 收费人员
{
private: double totalPrice = 0;
public:
void visit_MediEye(Medicine_Eye* mediEye)
{
totalPrice += mediEye->getPrice();
cout << " 收费人员统计药品 " << mediEye->getName() << " ,价格为: " << mediEye->getPrice() << "\n\n";
}
void visit_MediNose(Medicine_Nose* mediNose)
{
totalPrice += mediNose->getPrice();
cout << " 收费人员统计药品 " << mediNose->getName() << " ,价格为: " << mediNose->getPrice() << "\n\n";
}
double getTotalPrice() { return totalPrice; }
};
class Visitor_Distribute : public Visitor // 收费人员
{
public:
void visit_MediEye(Medicine_Eye* mediEye)
{
cout << " 药房人员分发药品 " << mediEye->getName() << "\n\n";
}
void visit_MediNose(Medicine_Nose* mediNose)
{
cout << " 药房人员分发药品 " << mediNose->getName() << "\n\n";
}
};
class Visitor_Trainer : public Visitor
{
public:
void visit_MediEye(Medicine_Eye* mediEye)
{
cout << " 健身教练认为要做眼保健操\n\n";
}
void visit_MediNose(Medicine_Nose* mediNose)
{
cout << " 健身教练认为要多跑步\n\n";
}
};
void Medicine_Eye ::accept(Visitor* visitor) { visitor->visit_MediEye(this); }
void Medicine_Nose::accept(Visitor* visitor) { visitor->visit_MediNose(this); }
int main()
{
Medicine_Eye mediEye;
Medicine_Nose mediNose;
Visitor_Charge visiCharge;
Visitor_Distribute visiDistri;
Visitor_Trainer visiTrainer;
mediEye.accept(&visiCharge);
mediEye.accept(&visiDistri);
mediEye.accept(&visiTrainer);
cout << " -------------------------------\n\n";
mediNose.accept(&visiCharge);
mediNose.accept(&visiDistri);
mediNose.accept(&visiTrainer);
cout << " 总的药钱: " << visiCharge.getTotalPrice() << endl;
return 0;
}
测试结果如下:
(24) 解释器模式 Interpreter 。一门计算机语言,必须背后有一个匹配的编译器对其支持,将其翻译为机器语言,此门新的编程语言才是有效的。编译器模式就是讲如何为一门语言编写匹配的编译器。为举例简单,规定本语言只支持整数的加减运算,没有括号,且表示整数的字符必须是单个的英文字母。整数与加减运算符之间,书写的时候不留空格。因为为语言编写编译器具有类似的地方,故称其为一种模式。
以下是例子代码:
#include<xstring>
#include<map>
#include<stack>
class Node // 对语言翻译的结果是将其组织成语法树。故需要定义树节点的结构
{
public:
char type; // 节点类型,取值: v + -
virtual ~Node() {}
Node(char c) : type(c) {}
virtual int interpret(map<char , int>& mapVar) = 0; // 形参对运算符节点没用,但对整数节点有用,返回变量名对应的值
};
class Node_Var : public Node // 变量节点
{
private : char varName ;
public:
Node_Var(char name, char type) : varName(name), Node(type) {}
int interpret( map<char, int>& mapVar) { return mapVar[varName]; }
};
class Node_Symbol : public Node // 运算符节点的父类
{
protected : Node* ptrLeft, * ptrRight;
public:
Node_Symbol(Node* left, Node* right, char type) : ptrLeft(left), ptrRight(right), Node(type) {}
Node* getLeftChild() { return ptrLeft; }
Node* getRightChild() { return ptrRight; }
};
class Node_Add : public Node_Symbol // 加号运算符
{
public:
Node_Add(Node* left, Node* right, char type) : Node_Symbol(left , right , type) {}
virtual int interpret(map<char, int>& mapVar) // 本函数可以被递归调用,返回左子树与右子树的值的和
{
return ptrLeft->interpret(mapVar) + ptrRight->interpret(mapVar);
}
};
class Node_Sub : public Node_Symbol // 减号运算符
{
public:
Node_Sub(Node* left, Node* right, char type) : Node_Symbol(left, right, type) {}
virtual int interpret(map<char, int>& mapVar) // 本函数可以被递归调用,返回左子树与右子树的值的差
{
return ptrLeft->interpret(mapVar) - ptrRight->interpret(mapVar);
}
};
Node* buildTree(string& expr) // #include<stack>
{
stack<Node* > stackNode; // 这个栈很有作用
Node* ptrLeft = nullptr, * ptrRight = nullptr;
for (int i = 0; i < expr.size(); i++)
switch (expr[i])
{
case '+':
ptrLeft = stackNode.top();
++i;
ptrRight = new Node_Var(expr[i], 'v');
stackNode.push( new Node_Add(ptrLeft , ptrRight , '+') );
break;
case '-':
ptrLeft = stackNode.top();
++i;
ptrRight = new Node_Var(expr[i], 'v');
stackNode.push(new Node_Sub(ptrLeft, ptrRight, '-'));
break;
default:
stackNode.push( new Node_Var(expr[i] , 'v') );
break;
}
return stackNode.top();
}
void release(Node * node) // 回收语法树占据的内存,可能被递归调用
{
if (node->type == 'v')
delete node;
else
{
release(((Node_Symbol*)node)->getLeftChild() );
release(((Node_Symbol*)node)->getRightChild() );
delete node;
}
}
int main()
{
string expr("a-b+c+d");
map<char, int> mapVar;
mapVar.insert(pair('a' , 7));
mapVar.insert(pair('b' , 9));
mapVar.insert(pair('c' , 3));
mapVar.insert(pair('d' , 2));
Node* root = buildTree(expr);
cout << " 表达式为: " << expr << "\n\n";
cout << " 结果为 : " << root->interpret(mapVar) << "\n\n";
release(root);
return 0;
}
测试结果如下:
接着为本解释器模式再举一例。机器人控制,为此创建一门语言,我们可以使用如下 的语句: left walk 15 and up run 20 。
该语言有如下几个关键字 : left 、 right 、 up 、 down ,表示运动的方向; walk 、 run 表示运动的方式 , 整数表示运动的距离;and 表示衔接两个运动。用空格分隔关键字。我们为其创建解释器。
#pragma warning(disable : 4996) // 必须要有,要不 strcpy 与 strtok 都会报错
#include<string>
#include<xstring>
#include<map>
#include<stack>
#include<vector>
class Node
{
public:
virtual ~Node() {}
virtual string interpret() = 0;
};
class Node_Direction : public Node
{
private: string direction;
public:
Node_Direction(const string& s) : direction(s) {}
virtual string interpret()
{
if (direction == "left") return "向左";
else if (direction == "right") return "向右";
else if (direction == "up") return "向上";
else if (direction == "down") return "向下";
else return "方向错误";
}
};
class Node_mode : public Node // walk or run
{
private: string mode;
public:
Node_mode(const string& s) : mode(s) {}
virtual string interpret()
{
if (mode == "walk") return "走步";
else if (mode == "run") return "跑步";
else return "动作错误";
}
};
class Node_Distance : public Node // 距离
{
private: string distance;
public:
Node_Distance(const string& s) : distance(s) {}
virtual string interpret() { return " " + distance + " 米 "; }
};
class Node_Sentence : public Node
{
private:
Node* ptrDistance; // 这里用父类指针,指向子类对象,否则报错
Node* ptrDirection;
Node* ptrMode;
public:
Node_Sentence(Node* pDirect, Node* pMode, Node* pDistance) : ptrDirection(pDirect), ptrMode(pMode), ptrDistance(pDistance) {}
Node* getDirection() { return ptrDirection; }
Node* getMode() { return ptrMode; }
Node* getDistance() { return ptrDistance; }
virtual string interpret()
{
return ptrDirection->interpret() + ptrMode->interpret() + ptrDistance->interpret() ;
}
};
class Node_And : public Node
{
private:
Node* ptrLeft , * ptrRight;
public:
Node_And(Node* pL, Node* pR) : ptrLeft(pL), ptrRight(pR) {}
Node* getLeft() { return ptrLeft; }
Node* getRight() { return ptrRight; }
virtual string interpret() { return ptrLeft->interpret() + " 又 " + ptrRight->interpret(); }
};
Node* buidTree(string & expr)
{
stack<Node*> stackNode;
vector<string> vecStr;
Node* pDirection = nullptr, * pMode = nullptr, * pDistence = nullptr, * pLeft = nullptr, * pRight = nullptr;
char* cache = new char[expr.size() + 1 ];
strcpy(cache , expr.c_str());
char* pstrChild = strtok(cache , " ");
while (pstrChild)
{
vecStr.push_back(string(pstrChild)); // 把原来的语句拆分成了一个个语法节点
pstrChild = strtok(NULL , " ");
}
delete[] cache;
for( auto iter = vecStr.begin() ; iter != vecStr.end() ; iter++ )
if (*iter == "and")
{
pLeft = stackNode.top();
++iter;
pDirection = new Node_Direction(*iter);
++iter;
pMode = new Node_mode(*iter);
++iter;
pDistence = new Node_Distance(*iter);
pRight = new Node_Sentence(pDirection, pMode, pDistence);
stackNode.push(new Node_And(pLeft , pRight));
}
else
{
pDirection = new Node_Direction(* iter);
++iter;
pMode = new Node_mode(* iter);
++iter;
pDistence = new Node_Distance(* iter);
stackNode.push(new Node_Sentence(pDirection , pMode , pDistence));
}
return stackNode.top();
}
void release(Node* ptr) // 本函数也会被递归调用
{
Node_And* pAnd = dynamic_cast<Node_And*>(ptr);
if (pAnd)
{
release(pAnd->getLeft()) ;
release(pAnd->getRight());
}
else
{
Node_Sentence* pSentance = dynamic_cast<Node_Sentence*>(ptr);
if (pSentance)
{
release(pSentance->getDirection());
release(pSentance->getMode());
release(pSentance->getDistance());
}
}
delete ptr;
}
int main()
{
string expr("right walk 15 and up run 30");
Node* ptrRoot = buidTree(expr);
cout << " 机器人控制代码为: " << expr << "\n\n";
cout << " 解释器解释后: " << ptrRoot->interpret() << "\n\n";
release(ptrRoot);
return 0;
}
测试结果如下:
经 MFC 框架测试,没有内存泄露:
至此,王老师的 《c++ 设计模式》 的课本例子,已抄写完毕,谢谢王老师。
谢谢