在数值模拟编程过程中,会遇到这样一个问题:有些参数或者物理量的值通过其他参数或者变量通过各种运算得到,如果这些参数和变量的值都是常量,一旦被初始化我们就不会再改变它,那么问题变得非常简单,例如:
const double pai = 3.1415926;
...
double variable = 2 * pai + 0.05;
但是很多时候变量variable依赖的参数或者值是一个变量,它的取值往往会因为各种因素变化,当程序运行时这些变量被不断的修改,并且这些修改是根据你调用时刻的上下文所决定,这意味着每次你需要用这些参数和值导出一个新的变量时,你都要显式的获取每个可能改变的参数的当前值。例如:
double s1;//s1是随着程序运行时间变化的变量。
double s2;//s2是随着当前实体三维空间坐标变化的变量。
double s3;//s3是随着当前实体三维空间坐标以及该处温度值变化的变量。
这时候有一个物理量由这三个变量导出:
double s4 = s1 6.24 + (s2 + s1) s3;
当你每次要使用s4时,你需要做什么:
s1 = s1_change_from_time();
s2 = s2_change_from_xyz(x,y,z);
s3 = s3_change_from_xyzt(x,y,z,t);
...
s4 = s1 / 6.24 + (s2 + s1) * s3;
如果还有变量依赖于s4呢?这样的累加会让你编写程序过程中不断重复同样的逻辑,极易出错,如果将来扩展程序,s4的求值新添加了扩展项,或者s2的改变不仅由xyz坐标决定,而且增添了新的影响变量,这些都意味着你要修改每一段重复的逻辑。
设计模式的意义就是逻辑复用代码复用,以增加系统的健壮性,可维护性和可扩展性。本文的目的就是提出一种设计模式(或者说一个框架)解决这个问题,达到解耦变量更新的逻辑和变量运算的逻辑,这意味着不必每次需要s4时都因为不得不跟新其依赖变量而重写其计算过程,减少出错概率。
首先用树的结构保留变量的运算逻辑:
s4 = s1 + s3 * s2--->
s4 = +
/ \
s1 *
/ \
s3 s2
见如下代码:基类node_是表示运算、变量和常量所有类的基类,类plus_、sub_、multi_和divi_是代表+-*/四个二元运算的类,flip是代表一元运算取负的类。
#include <memory>
using std::shared_ptr;
template<class T>
class node_ {
public:
node_() = default;
virtual ~node_() {
}
virtual double getValue() const = 0;
virtual void update(const T& t) = 0;
};
template<class T>
class op_ : public node_<T> {
public:
op_(shared_ptr<node_<T>> l,shared_ptr<node_<T>> r):left_(l),right_(r) {}
virtual ~op_() = default;
virtual void update(const T& t) override {
left_->update(t);
right_->update(t);
}
protected:
shared_ptr<node_<T>> left_;
shared_ptr<node_<T>> right_;
};
template<class T>
class plus_ : public op_<T> {
public:
plus_():op_<T>(nullptr,nullptr) {}
plus_(shared_ptr<node_<T>> left,shared_ptr<node_<T>> right): op_<T>(left,right) {}
virtual ~plus_() = default;
virtual double getValue() const override {
return this->left_->getValue() + this->right_->getValue();
}
};
template<class T>
class sub_ : public op_<T> {
public:
sub_() : op_<T>(nullptr,nullptr) {}
sub_(shared_ptr<node_<T>> left, shared_ptr<node_<T>> right) : op_<T>(left, right) {}
virtual ~sub_() = default;
virtual double getValue() const override {
return this->left_->getValue() - this->right_->getValue();
}
};
template<class T>
class multi_ : public op_<T> {
public:
multi_() : op_<T>(nullptr,nullptr) {}
multi_(shared_ptr<node_<T>> left, shared_ptr<node_<T>> right) : op_<T>(left, right) {}
virtual ~multi_() = default;
virtual double getValue() const override {
return this->left_->getValue() * this->right_->getValue();
}
};
template<class T>
class divi_ : public op_<T> {
public:
divi_() : op_<T>(nullptr,nullptr) {}
divi_(shared_ptr<node_<T>> left, shared_ptr<node_<T>> right) : op_<T>(left, right) {}
virtual ~divi_() = default;
virtual double getValue() const override {
return this->left_->getValue() / this->right_->getValue();
}
};
template<class T>
class flip_ : public node_<T> {
private:
shared_ptr<node_<T>> child_;
public:
flip_() : child_(nullptr) {}
flip_(shared_ptr<node_<T>> c):child_(c){}
virtual ~flip_() = default;
virtual void update(const T& t) override {
this->child_->update(t);
}
virtual double getValue() const override {
return 0.0 - this->child_->getValue();
}
};
constant_代表常量类,该类的对象在调用update时候不会发生任何事,并且该类的构造函数仅接受一个double类型的值,这意味着constant_的表现与我们平常意义上理解的常量一致。variable_代表变量类,我们注意该类的构造函数,其接受一个reflect_<T>类型的指针。这里的reflect_<T>是一个虚基类,而就是该类的派生类真正实现了“变量”这一概念,变量不是一个确切的值,根据具体情况,变量是一个接受某种输入(或者说上下文),输出一个值的函数。在variable_的update函数中,就是根据其reflect_from函数得到该变量在t情况下的确切值。而这个一直跟着我们的模板类型T是干嘛的也就明白了,该类型的对象代表了变量相关的上下文
template<class T>
class value_ : public node_<T> {
protected:
double v_;
public:
value_(double v):v_(v) {}
virtual ~value_() {
}
virtual double getValue() const override {
return v_;
}
};
template<class T>
class constant_ : public value_<T> {
public:
constant_(double v):value_<T>(v) {}
virtual ~constant_() {}
virtual void update(const T& t) override {
;
}
};
template<class T>
class reflect_ {
public:
virtual double reflect_from(const T& t) = 0;
};
template<class T>
class variable_ : public node_<T> {
private:
shared_ptr<reflect_<T>> ptr_;
double now_v_;
public:
explicit variable_(reflect_<T>* ptr):ptr_(ptr),now_v_(0.0) {}
double getValue() const override {
return now_v_;
}
void update(const T& t) override {
now_v_ = ptr_->reflect_from(t);
}
virtual ~variable_() {
}
};
举个例子,如果某个变量是根据三维空间的坐标在变换,那么我们要将T替换为如下类型:
struct position {
double x;
double y;
double z;
}
接下来是第三部分,类coeff通过重载运算符,实现了coeff和coeff对象在运算过程中构建了代表运算的树结构。一个coeff对象内含一个shared_ptr<node_<T>> root_对象,该对象要么是一个变量,要么是一个常量,要么是一个代表运算的类。
template<class T>
class coeff {
public:
coeff():root_(nullptr) {}
coeff(shared_ptr<node_<T>> v):root_(v) {}
coeff(const coeff& other) = default;
coeff(coeff&& other) = default;
coeff& operator=(const coeff& other) = default;
coeff& operator=(coeff&& other) = default;
coeff operator+(const coeff& other) const {
coeff result;
shared_ptr<plus_<T>> tmp(new plus_<T>(root_, other.root_));
result.root_ = tmp;
return result;
}
coeff operator-(const coeff& other) const {
coeff result;
shared_ptr<sub_<T>> tmp(new sub_<T>(root_, other.root_));
result.root_ = tmp;
return result;
}
coeff operator*(const coeff& other) const {
coeff result;
shared_ptr<multi_<T>> tmp(new multi_<T>(root_, other.root_));
result.root_ = tmp;
return result;
}
coeff operator/(const coeff& other) const {
coeff result;
shared_ptr<divi_<T>> tmp(new divi_<T>(root_, other.root_));
result.root_ = tmp;
return result;
}
coeff operator-() const {
coeff result;
shared_ptr<flip_<T>> tmp(new flip_<T>(root_));
result.root_ = tmp;
return result;
}
double getValue() const {
return root_->getValue();
}
void update(const T& t) {
root_->update(t);
}
private:
shared_ptr<node_<T>> root_;
};
template<class inf, class ref, class... Types>
shared_ptr<variable_<inf>> make_variable(Types&&... args) {
return shared_ptr<variable_<inf>>(new variable_<inf>(new ref(std::forward<Types>(args)...)));
}
template<class inf>
shared_ptr<constant_<inf>> make_constant(double v) {
return shared_ptr<constant_<inf>>(new constant_<inf>(v));
}
最后看一个example例子:
#include "expression.h"
#include <iostream>
struct position {
double x;
double y;
double z;
double t;
position(double xx, double yy, double zz,double tem) :x(xx), y(yy), z(zz),t(tem) {
}
};
class tem : public reflect_<position> {
public:
tem() = default;
double reflect_from(const position& p) override {
return p.x + p.y * 0.2 + p.z * 0.2;
}
};
using co = coeff<position>;
int main() {
auto v1 = make_variable<position,tem>();
auto v2 = make_constant<position>(2);
co c = co(v1) + co(v2) * co(v1);
c = c * co(v1);
c.update(position(2, 3, 4, 2.4));
std::cout << c.getValue();
system("pause");
return 0;
}
注意:这里的position类就是代替上述中的模板类型T的类型,当update的时候输入一个该类型的对象,并在内存中代表表达式的树中依次调用update函数,树中的运算符类对象会将该对象传递给其左右子表达式的update函数,而常量类对象不做任何处理,变量类对象通过构造时候传入的reflect对象调用reflect_from(const position& p)得到具体的值。
这里的tem类就是reflect类的一个派生类,这里它代表了三维空间中随着空间变化的温度值。
以上,当变量c被构造后,任何时候需要获取其具体值时,只要调用update函数并传入代表当前情况的position对象,就会完成所有变量的更新。然后调用getValue函数获得当前值。
这种方法类似于设计模式中的观察者模式但又不完全相同。我把它称为状态-反馈模式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。