组合模式目标:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
透露一下:这个例子可以说是组合模式的典型应用,据说(作者说)某个编译器开发团队用了两个半月的时间实现了表达式求值,被作者用十几行代码就这样实现了。
需求:表达式求值,是编译器的重要组件,本例你能找到的实际代码应该不多,因为是本人根据《C++沉思录》里面的例子亲自敲出来的(当然都是作者的功劳)。目的在于支持各种一元运算,二元运算甚至更多的运算都加入到表达式求值中,程序方便扩展,使用简单。
代码展示:说实话这个例子还是不太好理解的,尤其对初学者,因为比较抽象,但是如果先给出他怎么使用这个结果,就比较容易看清楚为什么要这么组织了。每次过段时间就会忘记作者怎么实现的,所以这里集中展示,方便以后回忆。
首先是main函数
从main函数你可以看出作者打算创造一个什么样的东西,这个东西至少要完成哪些事情,之后你可以猜测要完成这件事你要做哪些准备
#include "ExpressNode.h"
#include "ValueNode.h"
#include "UnaryNode.h"
#include "BinaryNode.h"
#include "Express.h"
void main()
{
Express t=Express(3);
t=Express('+',t,12);
cout<<t<<" = "<<t.eval()<<endl;
Express y=Express('-',4);
cout<<y<<" = "<<y.eval()<<endl;
Express t1=Express('*',Express('-',5),Express('+',3,4));
cout<<t1<<" = "<<t1.eval()<<endl;
t=Express('*',t1,t1);
Express t2=Express('*',t,t);
cout<<t2<<" = "<<t2.eval()<<endl;
}
运行结果:
看到了吧,上图就是表达式求值,看起来是不是很方便,这个东西可以打印你的表达式,还可以直接求出表达式的值,这个自然的需求蕴含着一个事实:表达式有各种各样的,一元操作符,二元操作符,单个数值也是表达式,所有类型的表达式都支持输出,都支持计算结果。当然还可以添加三元操作符表达式。
这样一来就至少有一个表达式类是客户第一要接触的:Express,这个类的接口如你想象应该是下面的样子
Express类
Express.h
#pragma once
#include "ExpressNode.h"
#include <iostream>
using namespace std;
class Express
{
public:
Express(int);//ValueNode(int) Express(3)
Express(char,const Express);//UnaryNode(char,int) Express('+',t,12)
Express(char,const Express,const Express);//BinaryNode(char,int,int) Express('+',3,4)
Express(const Express&);
Express& operator=(const Express&);
~Express(void);
friend ostream& operator<<(ostream& os, const Express& e)
{
os<<*(e.p);
return os;
}
int eval() const;
private:
class ExpressNode* p;//具体的功能由这个类实现,这个类派生了各种各样的表达式
};
Express.cpp
#include "Express.h"
#include "ValueNode.h"
#include "UnaryNode.h"
#include "BinaryNode.h"
Express::Express(int a)
{
p=new ValueNode(a);
}
Express::Express(char c, const Express e)
{
p=new UnaryNode(c,e);
}
Express::Express(char c,const Express el,const Express er)//BinaryNode(char,int,int)
{
p=new BinaryNode(c,el,er);
}
Express::Express(const Express& e1)
{
p=e1.p;
p->setUse(p->getUse()+1);
}
Express& Express::operator=(const Express& e1)
{
(e1.p)->setUse((e1.p)->getUse()+1);
p->setUse(p->getUse()-1);
if(p->getUse()==0)
delete p;
p=e1.p;
return *this;
}
Express::~Express(void)
{
p->setUse(p->getUse()-1);
if(p->getUse()==0)
delete p;
}
int Express::eval() const
{
return p->eval();
}
从Express的接口可以看出,Express创建对象的时候交给了具体的表达式类,而用基类指针实现多态,来达到统一计算,统一输出表达式的目的。
下面就是各种表达式的基类:ExpressNode,这个类是所有表达式的一般形式,这个类的接口要求各种表达式都要实现。
ExpressNode.h
#pragma once
#include<iostream>
using namespace std;
class ExpressNode
{
public:
friend class Express;
int getUse(void) const;
void setUse(int);
friend ostream& operator<<(ostream& os,const ExpressNode& ExprNode)//(1)输出表达式自身
{
ExprNode.print(os);
return os;
}
ExpressNode(void):use(1){}
virtual ~ExpressNode(void);
protected:
virtual void print(ostream& os) const=0;
virtual int eval() const=0;//(2)计算表达式的值
private:
int use;
};
ExpressNode.cpp
#include "ExpressNode.h"
ExpressNode::~ExpressNode(void)
{
}
int ExpressNode::getUse() const
{
return use;
}
void ExpressNode::setUse(int use1)
{
use=use1;
}
这个类看起来什么都没做,只是提供一个一致的界面让子类去实现,唯一的一个整形变量来保存引用计数,使得表达式不会大量复制拷贝
下面就是各种表达式子类的实现了:数值表达式ValueNode(表示数值常量)、一元表达式UnaryNode(正、负数运算)、二元表达式BinaryNode(两个表达式相运算+ - * /加、减、乘、除)
ValueNode.h
#pragma once
#include "ExpressNode.h"
#include "Express.h"
class ValueNode :
public ExpressNode
{
public:
friend class Express;
ValueNode(void);
ValueNode(int value1);
~ValueNode(void);
private:
void print(ostream& os) const;
int eval() const {return value;}
int value;
};
对数值表达式求值就是自己保存的值
ValueNode.cpp
#include "ValueNode.h"
ValueNode::ValueNode(void)
{
}
ValueNode::ValueNode(int value1):value(value1)
{
}
ValueNode::~ValueNode(void)
{
}
void ValueNode::print(std::ostream& os) const
{
os<<value;
}
输出一个数值表达式就是输出自己保存的值
UnaryNode.h
#pragma once
#include "Express.h"
#include "ExpressNode.h"
class UnaryNode :
public ExpressNode
{
public:
friend class Express;
UnaryNode(void);
UnaryNode(char c,class Express left1);
~UnaryNode(void);
private:
void print(ostream& os) const;
int eval() const ;
char opend;
class Express left;
};
从这里可以看出,正负运算的基础是一个一般意义上的表达式,任何复杂的表达式都可以加上正负运算。
UnaryNode.cpp
#include "Express.h"
#include "UnaryNode.h"
UnaryNode::UnaryNode(char c,class Express left1):opend(c),left(left1)
{
}
UnaryNode::~UnaryNode(void)
{
}
void UnaryNode::print(std::ostream &os) const
{
os<<"("<<opend<<left<<")";
}
int UnaryNode::eval() const
{
if(opend=='-')
return (-1)*left.eval();
throw "error, bad op int UnaryNode";
}
BinaryNode.h
#pragma once
#include "ExpressNode.h"
#include "Express.h"
class BinaryNode :
public ExpressNode
{
public:
friend class Express;
BinaryNode(void);
BinaryNode(char,class Express,class Express);
~BinaryNode(void);
private:
void print(ostream&) const;
int eval() const;
char opend;
class Express left;
class Express right;
};
从这里可以看出,二元表达式就是一个操作符,两个一般意义上的表达式,具体的工作还是往下分派,自己只做操作符要做的事。输出也是往下委派。
BinaryNode.cpp
#include "BinaryNode.h"
BinaryNode::BinaryNode(char c,class Express left1,class Express right1)
:opend(c),left(left1),right(right1)
{
}
BinaryNode::~BinaryNode(void)
{
}
void BinaryNode::print(ostream& os) const
{
os<<"("<<left<<opend<<right<<")";
}
int BinaryNode::eval() const
{
int op1=left.eval();
int op2=right.eval();
if(opend=='+') return op1+op2;
if(opend=='-') return op1-op2;
if(opend=='*') return op1*op2;
if(opend=='/'&& op2!=0) return op1/op2;
throw "error, bad operation in BinaryNode";
}
最终所有的求值运算都会落到一个数值表达式的头上,输出的过程就是一个在树上自底向上输出最终汇总的过程。
这个例子可以说是组合模式的典型应用,据说(作者说)某个编译器开发团队用了两个半月的时间实现了表达式求值,被作者用十几行代码就这样实现了。