C++之无聊小技巧三

上一篇扯了一点设计模式,本文接着讨论。以下内容仅供娱乐。

先看一个朴实的例子:

struct Widget {};
struct WidgetManager {
  Widget *getWidget() {
    Widget *pw = this->createWidget();
    widgets.push_back(pw);
    return pw;
  }
private:
  virtual Widget *createWidget() { return new Widget; }
  vector<Widget *> widgets;
};
struct Bolt : Widget {};
struct BoltManager : WidgetManager {
  Widget *createWidget() { return new Bolt; }
};

这是个工厂方法,我觉得它的别名“虚构造器”更能表达意图。只不过当流行的写法不一定使用多态。比如用switch:

struct WidgetManager {
  enum { BOLT, NUT, RIVET } widget_;
  Widget *createWidget() {
    switch (widget_) {
    case BOLT:
      return new Bolt;
    case NUT:
      return new Nut;
    case RIVET:
      return new Rivet;
    default:
      return new Widget;
    }
  }
};

效果是一样的。20年前的工程实践绝对提倡用多态,现在就见智见仁。特别是当前各种分布式,跨进程接口,消息格式大多是字符串,用多态反而得加一层适配。

为了体现娱乐精神,再做一个静态版:

template <typename T> struct WidgetFactory{
   Widget *createWidget() { return new T; }
};

这应用场景就非常有限了,还可以玩一些花式模板技巧,有缘再谈。

说道多态和switch,顺便讨论一下应用环境最苛刻的访问者模式。先看这个场景:

struct WidgetManager {
  void Info(Widget &w) {
    if (dynamic_cast<Bolt *>(&w)) {
      BoltInfo();
    } else if (dynamic_cast<Nut *>(&w)) {
      NutInfo();
    }
  }
  void NutInfo() { cout << "nut" << endl; }
  void BoltInfo() { cout << "bolt" << endl; }
};

Widget类只封装稳定部分,它的行为主要在Manager。效果上,行为的变化只涉及修改Manager而不打破依赖链条。传统看法认为,生硬的switch不具扩展性,所以搞了一个访问者出来:

struct Bolt;
struct Nut;
struct WidgetManager {
  virtual void NutInfo(Nut &nut) { cout << "nut" << endl; }
  virutal void BoltInfo(Bolt &bolt) { cout << "bolt" << endl; }
};

struct Bolt : Widget {
  void accept(WidgetManager &v) { v.BoltInfo(*this); }
};
struct Nut : Widget {
  void accept(WidgetManager &v) { v.NutInfo(*this); }
};

上面的例子效果类似(为了对照,我故意将Visitor写成Manager)。有什么行为调整,继承Visitor就行了。但这里还有问题,你必须前置声明数据类,代码才能编译通过。如果直接使用WidgetManager,岂不是无法访问数据?所以考虑到程序员都不自觉,Visitor应该是抽象类。也就是你需要使用Visitor的衍生类,并定义在数据实体之后。

我的天,用搞这么繁琐?本来就是个switch的事。况且很多设计都不分离数据和行为。我只能猜测超大型项目中Visitor模式有用武之地…前提是它得面向对象。相对而言,个人感觉将dynamic_cast用作类型匹配,反而更具启发性。

设计模式除了解决回调问题,就是类型匹配了。即使你没特意去学,也能穷举出这些代码tricks。各种软件设计的口号本质都是解耦,解耦,解耦。配合面向对象就是依赖倒置,接口隔离,开闭原则。再配合代码tircks就是设计模式。这些问题十几年前就被人讨论,我就不画蛇添足了。

Leave a Reply

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