首页 > 代码库 > 设计模式(二)组合模式Composite(表达式求值)

设计模式(二)组合模式Composite(表达式求值)

组合模式目标:将对象组合成树形结构以表示部分整体的关系,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";
}

最终所有的求值运算都会落到一个数值表达式的头上,输出的过程就是一个在树上自底向上输出最终汇总的过程。


这个例子可以说是组合模式的典型应用,据说(作者说)某个编译器开发团队用了两个半月的时间实现了表达式求值,被作者用十几行代码就这样实现了。














设计模式(二)组合模式Composite(表达式求值)