C++之无聊小技巧二

接下来扯一点设计模式。一般的业务框架会包含大量的模板方法模式,它的目的主要是为了复用代码。看个简单例子:

struct SessionManager {
  void execute() {
    open();
    run();
    close();
  }
private:
  void open() { cout << "session start" << endl; }
  void close() { cout << "session end" << endl; }
  virtual void run() { cout << "run" << endl; }
};
struct Printer : SessionManager {
  void run() { cout << "print ABC" << endl; }
};
int main() { 
  SessionManager *a = new Printer(); 
  a->execute();
/*
print: 
  session start
  print ABC
  session end
*/
}

由于开关操作稳定不变,可以将他们做成模板,易变不稳定的执行代码可以延迟实现。上面这个是通用写法,也就是说你在Java,C#也可以这么写。前一篇讨论过基于策略的设计,实现上借助了延迟继承。对模板方法也可以那么实现,只需稍稍改动:

template <typename T> struct SessionManager : T {
  void execute() {
    open();
    T::run();
    close();
  }

private:
  void open() { cout << "session start" << endl; }
  void close() { cout << "session end" << endl; }
  virtual void run() { cout << "run" << endl; }
};
struct Printer {
  void run() { cout << "print ABC" << endl; }
};
int main(){
  SessionManager<Printer> a;
  a.execute();
}

这可以说是个静态的模板方法。再想想,设计模式本来不就说优先组合,而不是继承么?对于执行函数run(),传个对象给它不就完了:

struct SessionManager {
  void execute() {
    open();
    run();
    close();
  }
  SessionManager(function<void()> run) : run(run) {}

private:
  void open() { cout << "session start" << endl; }
  void close() { cout << "session end" << endl; }
  function<void()> run;
};
void print(){
  cout << "ABC" << endl;
}
int main() {
  SessionManager a(print) ;
  a.execute();
}

是不是即容易实现,又好用。呃,不就是个回调函数?都这么写了还能叫模板方法么…于是设计模式就慢慢被淘汰掉了…

接着再看装饰器模式。它的效果有利于代码复用,但是更加体现的是单一职责。看例子:

struct Printer {
  void print() { cout << msg << endl; }
  string msg = "ABCDEFG";
};
struct LineDecor {
  LineDecor(Printer printer) : printer(printer) {}
  void print() {
    cout << string(printer.msg.size(), '-')  << endl;
    printer.print();
    cout << string(printer.msg.size(), '-')  << endl;
  }
  Printer printer;
};
int main(){
  Printer p;
  p.print();
//print: ABCDEFG
  LineDecor dp(p);
  dp.print();
/*
print:
  -------
  ABCDRFG
  -------
*/
}

这既视感…上述例子应该形象地表达出了装饰。同样还可以利用延迟继承实现一个静态版:

template <typename T> struct LineDecor : T {
  void print() {
    cout << string(T::msg.size(), '-') << endl;
    T::print();
    cout << string(T::msg.size(), '-') << endl;
  }
};
//usage: LineDecor<Printer> dp;

就我个人认知,在抽象和封装时,首先会考虑状态,比如这个例子的状态就是msg。我倾向于只将有状态的数据,行为封装打包成Class。像上面这个Printer,大多数情况下对用户来说是无状态的。所以有了下面这种写法:

auto print = [](const string &msg){
  cout << msg << endl;
};
auto lineDecor = [](function<void(const string&)> func){
  return [&](const string &msg){
    cout << string(msg.size(), '-') << endl;
    func(msg);
    cout << string(msg.size(), '-') << endl;
  };
};
lineDecor(print)("haha");

啊,再一次惊叹,居然可以这么瞎搞。看起来像是模板,不过尖括号换成了圆括号。其实这里应用了柯里化,效果是将输入函数加工后再返回出去。按照这种思路,对于执行函数已经是终点的,比如之前的SessionManager可以这么写:

auto sessionManager = [](Session *session, function<void()> run){
  return [=](){
    session->open();
    run();
    session->close();
  };
};
auto execute = sessionManager(dbSession, ::run);
execute();

呃,仔细看看,这不就是Javascript的Prototype?函数和对象在很大程度上可以互相替换,它们的主要分歧在于对状态的态度——函数式提倡无状态,面向对象倾向于封装状态。在我看来,状态都是其次,函数式的表达力明显更胜一筹。再回到原本的话题,代码都这样了还能叫设计模式么?当然,你可以认为万物皆模式……

我猜想,一部分设计模式的初衷是解决函数回调的缺陷(类似以前的Java,没有直接的回调方法)结果搞出了一堆更复杂的东西。好在写这篇博文时已经2018年了,模式神教大多已经消散。不过我听说貌似元编程神教正在慢慢兴起:P

Leave a Reply

Your email address will not be published. Required fields are marked *