In the process of numerical simulation programming, we will encounter such a problem: the values of some parameters or physical quantities are obtained through various operations of other parameters or variables. If the values of these parameters and variables are constant, we will not change them once they are initialized. Then the problem becomes very simple, such as:

Const double pai = 3.1415926; . Double variable = 2 * pai + 0.05;Copy the code

But most of the time variable variable dependent parameter or value is a variable, its value will often because of various factors change, when the program is running these variables have been modified, and these changes are based on your call is determined by the context of The Times, this means that every time you need to these parameters and the value is used to derive a new variable, You explicitly get the current value of each parameter that can change. Such as:

double s1; //s1 is a variable that changes with program runtime. double s2; //s2 is the variable that changes with the current 3d space coordinates of the entity. double s3; //s3 is a variable that changes with the current 3d space coordinates of the entity and the temperature value there.Copy the code

There is a quantity derived from these three variables:

Double s4 = s1 * 6.24 + (s2 + s1) * s3;Copy the code

Here’s what you need to do every time you use the 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;Copy the code

What if there are other variables that depend on S4? This accumulation can leave you writing the same logic over and over again, which is error-prone. If you extend the program in the future, the evaluation of S4 will add a new extension, or s2 will change not only by xyz coordinates but by adding new influence variables, which means that you will have to modify each repeated piece of logic. The purpose of design patterns is to reuse logic and code to increase the robustness, maintainability and extensibility of the system. The purpose of this paper is to propose a design pattern (or framework) to solve this problem, which decouples the logic of variable updating from the logic of variable operation. This means that s4 does not have to rewrite its calculation process every time it needs to update its dependent variables and reduce the probability of error.


First, use the tree structure to preserve the operation logic of variables: S4 = s1 + S3 * s2– >

 s4 =            +
                / \
               s1  *
                  / \
                 s3 s2
Copy the code

Node_ is the base class that represents all classes for operations, variables, and constants. Classes plus_, sub_, multi_, and divi_ are the classes that represent the +-*/ binary operations. Flip is the class that represents the negative of unary operations.

#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 {
    returnthis->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 {returnthis->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 {returnthis->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 {returnthis->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 {return0.0 - this - > child_ - > getValue (); }};Copy the code

Constant_ represents a constant class whose object does not happen when update is called, and whose constructor only accepts a value of type double. This means that constant_ behaves as we normally understand constants. Variable_ represents the variable class, and notice that the constructor of that class takes a pointer of type reflect_. Here reflect_ is a virtual base class, and it is the derived class of this class that really implements the concept of “variable”. ** A variable is not an exact value, but a function that takes some input (or context) and outputs a value, depending on the situation. ** In the variable_ update function, the exact value of the variable in the t case is obtained according to its reflect_from function. It’s clear what the template type T that follows us around is, and the object of that type represents the context of the variable

template<class T>
class value_ : public node_<T> {
protected:
  double v_;

public:
  value_(double v):v_(v) {}
  virtual ~value_() {

  }
  virtual double getValue() const override {
    returnv_; }}; 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_() {}};Copy the code

For example, if a variable is being transformed according to coordinates in three dimensions, we would replace T with the following type:

struct position {
double x;
double y;
double z;
}
Copy the code

Next comes the third part, where the coeff class overloads operators to build a tree structure for coeff and coeff objects during operations. A COeff object contains a shared_ptr<node_> root_ object, which is either a variable, a constant, or a class representing an operation.

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) {
  returnshared_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));
}
Copy the code

Here’s a final 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 {
    returnP.x + P.y * 0.2 + p.z * 0.2; }}; using co = coeff<position>; intmain() { 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.u pdate (position (2, 3, 4, 2.4)); std::cout << c.getValue(); system("pause");
  return 0;
}
Copy the code

Note: The position class replaces the template type T above. When you update, enter an object of this type and call the update function in the tree representing the expression in memory. The operator class object in the tree passes the object to the update function of its left and right subexpression. While the constant does nothing, the variable gets its value by calling reflect_from (const position& p) from the Reflect object passed in at construction time. The TEM class here is a descendant of Reflect, where it represents the spatially varying temperature in three dimensions. Above, when variable C is constructed, any time you need to get its specific value, you simply call the update function and pass in the position object that represents the current situation, and all variables are updated. The getValue function is then called to get the current value. This approach is similar to but not identical to the observer pattern in design patterns. I call it the state-feedback model.