SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

よく使うC++のイディオム 「NVI」と「RAII」


  • X ポスト
  • このエントリーをはてなブックマークに追加

 アプリケーションを作るには、"何をつくるか(what)"、そしてそれを"どうつくるか(how)"を考え、しかるのちそれがコードとして書き起こされます。コーディングの段階では"いかにコンパクト/エレガント/堅牢につくるか"などを考慮した実装デザインがその後のデバッグや拡張/変更/保守に大きな影響を及ぼします。実装時のちょっとした工夫:NVIとRAIIについてざっくりと解説します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

NVI:Non virtual Interface

 NVI(Non Virtual Interface)って、ご存じですか? 実装設計におけるオススメのポリシー/スタイルの一つなんですけど、そんな難しい話じゃありません。平たく言えば「パブリックメソッドをvirtualにしない」という実装上の制約です。それって何がオイシイんでしょう。

 日付を扱うアプリケーションのいち部品として、こんなのを考えました:

list01
class DateUtil {
public:
  const char* dayOfWeek(int nday) const;
  /* nday で与えられた 0,1...6 に対し、それぞれ
   * "日曜","月曜" ... "土曜" を返す */
  ...
};

const char* DateUtil::dayOnWeek(int nday) const {
  switch ( nday ) {
  case 0 : return "日曜";
  case 1 : return "月曜";
  ...
  case 6 : return "土曜";
  default: return nullptr;
}

 どこにでもありそうな、どってことのないコードです。日本語だけでなく英語にも対応しましょう。

list02
class DateUtil_ja {
public:
  const char* dayOfWeek(int nday) const;
  /* "日曜","月曜" ... "土曜" を返す */
  ...
};

class DateUtil_en {
public:
  const char* dayOfWeek(int nday) const;
  /* "Sun","Mon" ... "Sat" を返す */
  ...
};

 ワールドワイドな市場を狙ったアプリケーションの部品なら、これまたありがちなスペックですよね。

 ユーザのお好みやlocaleの設定次第で日/英をコロコロ切り替えたいならきっと:

list03
enum class language { ja, en };

class DateUtil { // 基底クラス
public:
  virtual const char* dayOfWeek(int nday) const=0;
  static DateUtil* create(language lang);
  ...
};

class DateUtil_ja : public DateUtil {
public:
  virtual const char* dayOfWeek(int nday) const;
  /* "日曜","月曜" ... "土曜" を返す */
  ...
};

class DateUtil_en : public DateUtil {
public:
  virtual const char* dayOfWeek(int nday) const;
  /* "Sun","Mon" ... "Sat" を返す */
  ...
};

DateUtil* DateUtil::create(language lang) {
  switch ( lang ) {
  case language::ja : return new Dateutil_ja();
  case language::en : return new Dateutil_ja();
  default:            return nulllptr;
}

 まぁ、まずまず妥当なデザインでしょうか。

 ここでNVIを適用します。NVIは「パブリックメソッドをvirtualにしない」がルールなので、dayOfWeek()をvirtualにできません。じゃぁどうするかっつーと、virtual仮想関数呼び出しのためにワンクッション置くんですわ:

list04
class DateUtil { // 基底クラス
public:
  const char* dayOfWeek(int nday) const {
    return do_dayOfWeek(nday); // dayOfWeekの"本体"を呼ぶ
  }
  static DateUtil* create(language lang);
protected:
  virtual const char* do_dayOfWeek(int nday) const =0;
...
};

class DateUtil_ja : public DateUtil {
protected:
  virtual const char* do_dayOfWeek(int nday) const;
  /* "日曜","月曜" ... "土曜" を返す */
  ...
};

class DateUtil_en : public DateUtil {
protected:
  virtual const char* do_dayOfWeek(int nday) const;
  /* "Sun","Mon" ... "Sat" を返す */
  ...
};

 動的に差し替わるvirtual関数をprotected部に置き、publicな関数からそいつを呼び出すことで実装しています。そのままprotectedなdo_dayOfWeek()に丸投げ/横流しですから、コードを無駄にややこしくしただけに見えます。

  • 「同じような処理があちこちにバラ撒かれる(Code Clone)ことを避けたい」
  • 「インターフェースの変更が実装に及ぼす影響を小さく抑えたい」

を実現するスタイルの一つがNVIです。

 public部にメソッドを置くということはすなわち、"使う人"にインターフェースを提供することを意味します。それと同時にそのメソッドを"作る人"に入出力の仕様を規定することになります。

 この両者が強く結びついていると一方の変更が他方に影響を及ぼします。そのこと自体はvirtualでないメソッドでも同じことなのですが、virtualメソッドのインターフェースの変更はそれを再定義しているすべての箇所に累が及ぶので、影響範囲がより広くかつ重大です。

 例えばこんな仕様変更:

 「dayOfWeek(int nday)だけどさ、ndayが0~6以外だったらdomain_error例外を投げてくんない?」

 dayOfWeek()がvirtualで、各導出クラスで再定義されていたらこの変更は導出クラスすべてにおよびます。この変更が身内の中だけならまだいいんですけど、ヨーロッパ支部のフランス/ドイツ対応チームがDateUtilからDateUtil_fr/DateUtil_deを導出してたら...

 NVIならば変更箇所は基底クラスの一箇所だけ。

list05
const char* DateUtil::dayOfWeek(int nday) const {
  if ( nday < 0 || nday > 6 ) {
    throw std::domain_error("invalid day-number");
  }
  return do_dayOfWeek(nday); // dayOfWeekの"本体"を呼ぶ
}

 「ついでにndayは1~7でお願い。処理中はCriticalSectionでガードしてね。んでもって戻り値はstd::stringにしてよ」なら、

list06
std::string DateUtil::dayOfWeek(int nday, CriticalSection* cs) const {
  if ( nday < 1 || nday > 7 ) {
    throw std::domain_error("invalid day-number");
  }
  EntrCriticalSection(cs);
  std::string result = do_dayOfWeek(nday-1); // dayOfWeekの"本体"を呼ぶ
  EntrCriticalSection(cs);
  return result;
}

 ちょっとした工夫なんだけど、クラス・ツリーの枝葉の多い構造であったならかなりの効果が期待できますよ。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
RAII:Resource Acquisition Is Initialization

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7294 2013/08/26 14:31

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング