在数值模拟编程过程中,会遇到这样一个问题:有些参数或者物理量的值通过其他参数或者变量通过各种运算得到,如果这些参数和变量的值都是常量,一旦被初始化我们就不会再改变它,那么问题变得非常简单,例如:

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函数获得当前值。
这种方法类似于设计模式中的观察者模式但又不完全相同。我把它称为状态-反馈模式。


p__n
491 声望10 粉丝

科学告诉你什么是不可能的;工程则告诉你,付出一些代价,可以把它变成可行,这就是科学和工程不同的魅力。