Non Virtual Interface

「Exceptional C++ Style」を買って読んだ。
C++ In Depth シリーズはたまに読むが買ったのは久しぶり。

「Effective C++」の3版も出ていてこちらも欲しいのだが、2版とダブる項目多いだろうし、立ち読みしてみると、丁寧語で書かれていてユーモアの部分がカットされていたのは残念。

Exceptional の方は、敬語でなくユーモアたっぷりにやってる。よかよか。

項目としては

こういうふうにすれ

という項目より

これは使うな

という項目が目立つ。
曰く

export は「まだ」使うな
auto は使うな
register は使うな
inline は使うな
例外指定はするな

といった具合。
auto, register, inline はもともと全然使ってなかったし、日記に書くほど「ありがたい教え」というわけではない。

読んで真新しかったのがタイトルの Non Virtual Interface パターン。

どゆのかというといわゆる Starategy の抽象クラスが

class C {
public:
    virtual void Operation(T1 arg1, T2 arg2);
};

と定義されていたとすると、この公開仮想インターインターフェイスは2つの役割を果たしているという。

1. 使う人に使い方(インターフェイス)を提供している
2. 派生クラスに「こういうふうに上書いてください」とカスタマイズの規約を提供している

という具合に。

2つの違った対象に「同時に」働きを提供しているというのは具合が悪く、分離すべきだという。
例えばお客さんから「公開インターフェイスにもひとつパラメータ増やしてぇ」と言われても、カスタマイズしてくれてる派生クラスさん達に頭を下げねばならず、派生クラスさんから「あのパラメータはいらんくなったよ」と言われてもお客さんの手前、勝手にインターフェイスを変えるわけにもいかない。

で、分離の方法なんすが pimpl イディオムだの Bridge パターンだの Body-Handle イディオムだの封筒クラスだのを使う手もあるが、もっと簡単に

class C {
public:
    // 公開インターフェイス
    void Operation(T1 arg1, T2 arg2) {
        OperationProc1(arg1, arg2);
        OperationProc2()
    }
private:
    // カスタマイズ用インターフェイス
    virtual void OperationProc1(T1 arg1, T2 arg2);
    virtual void OperationProc2();
};

とでもやっておいて、派生クラスには private なメソッドを上書いてもらえばよい、というのが、NVI パターンだそーだ。

クラス図としては Template Method パターンと同じになるが、目的が違うので違う名前をつけたそーな。Herb Sutter さんのオリジナルかどーかは知らんが、WinFX の実装では継承トゥリーを作るときは殆どこのパターンが使われてるそーな。
(private なメソッドでも仮想にしてオーバーライドできるというのは初めて知った。なんか親クラスから子クラスの private メソッドをコールしてるようでできないような感じでいたのだが)

ところでこれ、C++以外の C#, Java, D 等で抽象クラスの部分に interface を使おうとしてもできない。interface は実装を持てないし、そもそも interface で提供する公開インターフェイスが前述の2つの役割

お客さん、こー使ってね
カスタマイザーさん、こーいうふうに実装してね

というのを提供するためのものだからだ。

何が言いたいかというと、この件に関しては interface を持たない C++ の不利が消えるのだ。

NVI の良さは解った。
が、別に Herb Sutter さんが、既存の Strategy パターンが悪いので使うな、と言っているわけではない。関数オブジェクトでアルゴリズムを渡すときなど、おおいにつかうべきであろう。

しかし既存の 23 パターンのうち、継承と多態を使っているもので、NVIパターンを併用した方がよいのもありそうである。

ちょっと考えてみた。

Bridge
  そもそもの目的が、インターフェイスと実装を分離する
  という NVI と共通のものだから、Implementor の
  継承部分にわざわざ NVI を混ぜることはない。

State
  状態(State)と、状態を持っているもの(Context)との
  結びつきが密接であり、Context を使用する側としては
  むしろ状態のことにとんちゃくしたくないためにこの
  パターンを使う。
  とすれば、State の公開インターフェイスを Context を
  使う人にわざわざみせる機会は少ないと思われる。
  言い方を変えれば State は Context の「実装の詳細」で
  あってもともとお客さんからは隠されているのでわざわざ
  NVI を使ってさらに隠すことはない

Prototype
  これも NVI 使わないでいーんじゃねーかなー。
  だってインターフェイスが決まりきってるもん。
  メソッド名を clone にするか duplicate にするか
  の違いくらいで、引数なし、戻り値の型も決まり
  きってるしぃ。

Composite
  これ。これです。NVIを併用すべきなのは。
  Component にできることと、Leaf や Composite が
  行う実装の詳細とは分離すべき

Decorator
  ワカらん。判断できん。
  Decorator は Component が提供するインターフェイス
  実装するが、その実装手段として内部に持っている
  Component を使うこともあるというややこしい関係。
  NVI を使うと、外部向け提供のインターフェイスは、
  Component のみが行い、Decorator は実装にのみ精を
  出すということになるか。

Visitor
  そもそも私はコイツが嫌い。
  ダブルディスパッチで Element と Visitor の
  両方の型の違いを吸収する仕組みは見事とは
  思うが、完全にプログラムの都合のためにのみ
  存在するパターン。大体 Visitor は型が特定
  されたあと Element の公開メソッドを
  呼び出すだけで自分ではなにもしない。
  型の特定が終われば用なしの存在。
  大体、現実に対応するモデルが存在しない。
  Element を「訪問」するものって一体ナニ?
  見せて。あるのなら。

  と言った不満は横道であり、Visitor は
  Element が「自分で持っている」機能を
  実現するための「実装の詳細」であり、
  もともとお客さんなどの外部からは隠されて
  いることが多いと思われる。お客さんが
  「機能」Visitor を生成して Element に
  向かって「これをやれ」といって投げつけ
  る姿はちょっと想像し難い。

意外と NVI を併用すべきパターンが見つからない。
Interpreter とか、Command など、まだ考えがおよんでいないのだが、使ったほうがよいかも知れない。