ドラゴンボールで学ぶオブジェクト指向 改(第弐話)

ドラゴンボールでオブジェクト指向の学習という元ネタのアイデアがうけたのか、ドラゴンボールで学ぶオブジェクト指向 改 - 達人プログラマーを目指しての記事がかなり大きな反響がありました。前回の記事では、クラス図とデザインパターンを使った設計により、ドラゴンポールの世界をモデル化する例について説明しました。ただし、この説明の内容だと、静的なモデリングに偏っていたところもあり、具体的にどのようにプログラムの設計に役に立つのかという点が見えにくいところがあったかもしれません。今回は、前回説明しきれなかった、いくつかの点について、もう一歩踏み込んで説明してみたいと思います。

プログラミング言語におけるクラスとオブジェクト

前回の記事では、概念的なオブジェクト指向モデリングの観点から、

  • クラス→複数のオブジェクトが共通で満たす性質(属性や振る舞い)を概念として表現したもの。(地球人武道家、サイヤ人、戦闘型ナメック星人、種としてのセルなど)
  • オブジェクト→特定のクラスの性質を満たす具体的な人、物など(クリリン、ヤムチャ、悟空、ピッコロ、セルなど)

のようにクラスとオブジェクトを説明しました。この基本的な考え方を、どのようにプログラミング言語で実現するかについては、武道と同じでいろいろな流派があるのですが、C++やJavaなど現在主流のオブジェクト指向言語ではだいたい以下のようにマッピングされています。

  • クラスはデータ(フィールド、メンバ変数)とロジック(メソッド、メンバ関数)を定義するための言語構造
  • クラスの保持するデータをメモリ上に割り当てることでそのクラスのインスタンスであるオブジェクトを表現する
  • クラスはオブジェクト(の参照)を格納する変数の型になる(Javaにはクラスの型という側面のみに注目したインターフェースと呼ばれる特殊なクラスがある)
  • 実行時にサブクラスのメソッドを呼び分けることでポリモーフィズムを実現する(仮想関数呼び出し、遅延結合、ダイナミックバインディング)

そして、言語によってオブジェクトの生成のしかたにはさまざまな方法がありますが、ほぼすべての言語でnewと呼ばれる演算子を使って動的にオブジェクト(インスタンス)を生成して利用できるようになっています。生成したオブジェクトはヒープ(フリーストア)と呼ばれるメモリ領域に動的に確保されます。
たとえば、

地球人武道家 クリリン = new 地球人武道家("クリリン");
地球人武道家 ヤムチャ = new 地球人武道家("ヤムチャ");

などのように記述します。以上のコードでは地球人武道家クラスのインスタンスであるクリリンとヤムチャという二つのオブジェクトを生成し、そのオブジェクトを参照するクリリン、ヤムチャという変数に格納しています。Javaの場合new演算子の後にはコンストラクタの名前を指定するのですが、コンストラクタはクラス名であるという決まりがあるので、実質的にクラスからそのクラスに属するオブジェクトを生成してメモリに割り当て、そのメモリ領域を参照する変数に代入していると考えることができます。
ドラゴンボールの世界だと7個のドラゴンボールを集めても神龍に一つの願ごとしかできず、また、その神龍も万能ではなく、サイヤ人をやっつけられないなど制約が大きいのですが、オブジェクト指向プログラミングの世界だと、プログラマーは万能の神のようになれますね。newというお願いを何度でも実行でき、その都度、新しいオブジェクトが生成されるのです。もちろん、事前にオブジェクトを生成する基になるクラスを作成しておく必要がありますが、人間が思いつくあらゆるものをコンピューター上で「創造する」ことができます。

メソッド呼び出しは生成したオブジェクトに対する命令の実行

Cなどの構造化言語のプログラマーの方はオブジェクト型の変数は構造体の一種と考えるとよいと思いますが、データの集合を保持しているだけでなく、必殺技を繰り出すといった振る舞いも同時にカプセル化しているという点に注目してください。

クリリン.技を繰り出せ("気円斬");

のように「クリリン」オブジェクトに対してメソッドを呼び出すことで命令を与えることができます。クリリンに命令すると、後はクリリンが自発的に振舞い、技を実行するだけでなく、クリリンの気の残量も自動的に減少します。オブジェクトには振る舞いとデータが同時にカプセル化されているというポイントを思い出してください。気円斬を実行するとどれだけ気が減少するかといったことはクリリンや気円斬といったオブジェクト自身の中に組み込まれています。こういった様子を視覚的に表現するにはUMLのシーケンス図を利用します。

オブジェクト指向のプログラミングでは、このようにクラスから生成したオブジェクトに対してメソッドを通して命令を与えることで、処理を実行していきます。
なお、publicメソッドがオブジェクトに対する外部からの命令であるという点は英語で考えるとよりすっきりと理解できます。以下のエントリを参考にしてください。多くの日本人がオブジェクト指向プログラミングを苦手とするのは英語アレルギーだからか? - 達人プログラマーを目指して

変数の型とポリモーフィズム

クラスに対してnewを適用することでオブジェクトが生成できるのですが、クラスに親子関係がある場合、親クラスの型の変数に子クラスのインスタンスを代入できるという大切なポイントがあります。だから、

地球人武道家 クリリン = new 地球人武道家("クリリン");
サイヤ人 悟空 = new サイヤ人("悟空");
戦闘型ナメック星人 ピッコロ = new 戦闘型ナメック星人("ピッコロ");

と記述する代わりに

戦士 クリリン = new 地球人武道家("クリリン");
戦士 悟空 = new サイヤ人("悟空");
戦士 ピッコロ = new 戦闘型ナメック星人("ピッコロ");

のように記述することができます。サブクラスのインスタンスは親クラスのインスタンスであるという法則を思い出せば、言語上のこうした規則は自然に理解できると思います。このようにすべてのオブジェクトを同一の戦士型として扱うことができれば、たとえば、以下のように配列を使って一括処理するような場合にも便利です。

戦士[] Z戦士 = {new 地球人武道家("クリリン"), new サイヤ人("悟空"), new 戦闘型ナメック星人("ピッコロ"), ...};
for (戦士 せんし : Z戦士) { // 日本語だと大文字小文字の区別がないためクラス名とオブジェクト名の識別がちょっと苦しい
  せんし.技を繰り出せ(); // ポリモーフィズムによりメソッド名が同じでも型に応じた振る舞いをする。
}

オブジェクトの生成に関するパターン

このように、オブジェクト指向プログラミングでは、new演算子を使ってクラスからオブジェクトを生成することができますが、場合によってはオブジェクトが複雑な関連を持っていたりすると、初期化が複雑で難しいことがあります。デザインパターンではオブジェクトの生成や初期化に関するパターンがいくつか知られています。オブジェクトの生成を専任で受け持つクラスを別途設計します。このようなクラスは一般にファクトリーやビルダーなどと呼ばれます。
ドラゴンボールの世界では武道家を育成する場所がこのファクトリーやビルダーに相当すると考えることができます。戦士は生まれながらに技を習得しているというわけではないため、厳しい修行を積んで技を習得する必要があります。

クリリンが亀仙流に入門して技を習得するシーケンスを以下に示します。

流派の教義の多様性はStrategyパターンで吸収する

最後に、流派の教義について。同じ武道の流派でも鶴仙流と亀仙流では教義が大きく異なります。単純に流派そのものを継承させてこの違いを吸収することも不可能ではありませんが、

  • 流派間で共通していることもある
  • 共通部分を親クラスに持たせることで親子間のクラスの結合度が高くなる
  • 多くの言語では多重継承ができないため、複数のバリエーションを表現できない

などの欠点があります。複雑なドメインに対する設計では、変化する部分をインターフェースとして抽出し、そのサブクラスに個々のバリエーションを持たせるといった設計をします。これをStrategyパターンと呼んでいます。前節の図で教義インターフェースとその実装クラス(亀仙流の教義と鶴仙流の教義)との関係がこのStrategyパターンになっています。

最後に

一部には業務システムではオブジェクト指向は関係ないという意見もあるようですが、JavaやC#などの現代的なプログラミング言語を使った開発において、オブジェクト指向の考え方を習得することは欠かせないことであると私は思います。オブジェクト指向というのは武空術や、かめはめ波のように特殊な才能を持った人のみが使いこなせる技ではなく、呼吸法や筋力トレーニングなどのような基礎体力にあたる部分だと思います。しかし、そういった基礎技術を身につけているかどうかでプログラマーとしての戦闘力が桁違いに変わってくるということもまた事実です。
SI業界の世界では、以下のように基礎技術が軽視される傾向があることは非常に残念なことです。実際、スーパーSEとして一目置かれるような方からも、以下のような意見が語られています。
http://www.hitachi-system.co.jp/superse/vol2/

前回、プログラミング好きはSEには向いていないとお話しましたが、極端な話、できなくても構いません。実際には、プログラマを経験してからSEになるケースが多いようなので、スキルがある方がほとんどのようですが、SEアシスタントとしてスタートし、プログラミングをほとんど経験しないで独り立ちしたというケースも少なくありません。

ただし、以前プログラマーの成長を考えないSIerの仮説は間違っている - 達人プログラマーを目指してで私が書いたように、プログラマーの基本的な技術力を重視することで生産性、品質、保守性などを桁違いに上昇させることができると思いますし、SIerもプログラマーとしての基礎技術であるオブジェクト指向の習得などにもっと真剣にとりくむべきだと思います。
なお、本文とは関係ありませんが、YouTubeで集英社による悟空とアラレちゃんの震災に対する応援メッセージのビデオが紹介されています。収益は全額寄付されるようです。