挨拶
ヴァンガードの無限転生が発売されましたね。リンクジョーカー編の決戦弾だけあって、どのカードも魅力的です。特に収録枚数は少ないもののペイルムーンが面白いかなと思っています。ヴィーナスルキエは元のルキエからコストが減りつつスペコ性能が上がり自身のバンプが無くなったという内容ですが、ユニット設定から見てもワンマンな性格からサーバントへ信頼を寄せるユニットになっている点に面白さを感じます。先日ぶりです。カードファイター石田です。テーマがポケモンネタにも関わらずヴァンガードの話をしたところに意味はありません。
ロードマップ
- 前提条件となるポケモンのステータスの仕様を説明する
- 仕様に合わせてベースとなるクラス構造を考える <= イマココ
- メジャーどころを抜くためのS振り調整計算機を作る
- 不利な相手を想定した耐久調整用計算機を作る
ということで前回、ポケモンのステータス計算用の計算式と変数の話をしました。単に実装するだけであれば「この変数と計算式をプログラムに落とし込めば作れるよね」という段階ではあります。ところが、そこはそれ、今回のテーマはOOPです。「オブジェクト指向プログラム」です。なので、クラス構造を考えてプログラムしていく必要があります。
オブジェクト指向って?
端的に言えばオブジェクトという単位を基準にプログラムするということです。オブジェクトとは直訳すればモノです。ここで「現実世界の事象をモノという単位で捉え、それをプログラムに落とし込んでいく」とか、それの実例として「AppleクラスはColorプロパティを持っていて、そのインスタンスはRedまたはGreenを返すとか」いうような大袈裟なことを言う人が居ます。その説明は間違ってはいないと思いますが、僕個人としては「分からない人向けの説明」としては「より一層分からなくなるんじゃないの?」 的な印象を受けます。大体、その手の話をつきつめると、この世に相互作用を及ぼさないモノは無いので、世界全てをモデリングすることになっちゃいますし、果ては全てのクラスのスーパークラスにGenesisとかHolonが現れないといけなくなったりして宗教や哲学の世界に行き着いてしまいます。OOP怖い。なので、ちょっと原理主義的な視点は置いておいて、身も蓋も無い説明から始めようと思います。プログラムは、つまるところデータ構造とアルゴリズムです。このデータ構造とアルゴリズムをセットにしたクラスを定義し、そのクラスをインスタンス化した「オブジェクト」を中心にプログラムすることです。C言語で言う構造体に関数ポインタをくっ付けたモノだと思ってもOKです。それを踏まえてPerlとPHPのソースで比較してみましょう。と、用語の意味が分からないとアレなので、その前に用語説明を挟みます。
オブジェクト指向的な用語
- メソッド
- オブジェクトの持ってる振舞いです。要するに関数です。
- プロパティ
- オブジェクトの持ってる要素です。要するに変数です。
- インスタンス
- クラスを実体化したモノ。要するに構造体の変数です。
PHPのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php class xyzzy { private $hoge = null; public function setHoge ($hoge) { $this->hoge = $hoge; } public function getHoge () { return $this->hoge; } public function helloHoge () { print 'Hello, ' . $this->hoge; } } $obj = new xyzzy(); $obj->setHoge ('!hoge!'); $obj->helloHoge(); |
Perlのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use strict; package xyzzy; sub new { return bless {} } sub setHoge { my $self = shift ; $self->{hoge} = shift; } sub getHoge { my $self = shift ; return $self->{hoge}; } sub helloHoge { my $self = shift; print 'Hello, ', $self->{hoge}; } my $obj = xyzzy::new; $obj->setHoge('!hoge!'); $obj->helloHoge; |
全く同じことをしているソースです。見比べてみましょう。Perlの方は引数を表す配列から $self を取り出してから処理をする関数になっていますね。PHP で言うところの $this と Perl の $self は同じものです。Perlの方に着目して見てみれば、$selfのことを考慮しなければ、クラス内のメソッドだろうと、ただの関数に過ぎないことが分かると思います。その$self はと言えば、new 関数の中でbless 処理してreturnした無名ハッシュです。bless は、blessに渡された変数を通じてメソッドを呼び出した時(サブルーチンのリファレンスをデリファレンスして呼び出した時)、第一引数に、その変数(ここで言えば無名ハッシュ)を渡すという処理をするものです。要するに、単なる構造体の変数に関数ポインタをバインドしてなんらかの方法で自身の構造体の中身が見えれば、オブジェクト指向プログラミングは実現出来るってことですね。仰々しい名前と、誰かが語る哲学的なアレに翻弄されてよく分からなくなっている君も、これで別にオブジェクト指向が怖くなくなりましたね。これを現実世界に近しい構造に歩みよって定義していくと、直交性を保てる上に分かり易くなるよね?という話です。
直交性
ちょっと、直交性という言葉について触れておこうと思います。プログラムというデータ構造とアルゴリズムの取り扱いにおいて重視されるべき事は「直交性」です。その「直交性」を実現するための、いくつかの手段のうちの一つがオブジェクト指向プログラミングということです。
では、その「直交性」とは何か?というと、データ構造とアルゴリズムの取り扱い方のベストプラクティスです(つまり、データ構造とアルゴリズムの取り扱いについて、ちゃんとしたデザインをしないと、すぐプログラムはグチャグチャになるし迷走するしメンテナンスが大変になるよね、なんか良い方法は無いの?)。他にももっと良い方法はあるかも知れないし、オブジェクト指向は目的に対して多少冗長ではありますが、天才的な人じゃなくても扱い易く、後付けで既存の言語にも載せ易いということで広まったものだと思っています。
Wikipediaからの引用です。
情報科学における直交性とは、特定の機能体系内で例外的な振る舞いを行なう要素の多寡を指し示す慣用的な用語である。どの要素も本質的には同じように振る舞うならば直交性が高い、要素毎の癖が強いならば直交性が低い、と表現する。
機能の直交性が高いと言う事は、一つの要素について知れば他の要素でも類推が効くと言う事である。従って利用者にとっては、直交性の高さはその体系の使い勝手が良い事を意味する。
んー? ではさらに読み進めてみると
ソフトウェアでの直交性は、機能間の組み合わせや順序について依存関係が小さい事を意味する場合が多い。具体的には次のような事例を指す。
- オブジェクトへの操作があるなら逆操作もある。
- プロパティ間に依存関係がない。
- 不変契約が保持されている。
分かるような分からないような話ですね。厳密には違うかも知れませんが、つまり、データ構造とアルゴリズムがきれいに部品化されていて再利用性が高いということです。それらの部品の汎用性が高く、変に特化したところが少いということですかね。確か、ポールグレアムは直交性が高いということについてレゴブロックに例えていました。一つの機能を実現するソースコードがレゴブロック的であると扱い易いということですね。これは分かり易い例えだと思います。
ポケモンのクラス構造を考える
クラス構造を考える時、やり方は色々有るでしょうが、一番基本的なやり方だと個人的に思っているのが要件を文章に起こすことです。前回の前提となる知識の項を要約します。
ポケモンは、HP(略称:H)、物理攻撃力(略称:A)、物理防御力(略称:B)、特殊攻撃力(略称:C)、特殊防御力(略称:D)、スピード(略称:S)の能力値を持ち、各能力値は以下の計算式で算出される。
HP = { ( 種族値 × 2 + 個体値 + 努力値 ÷ 4 ) × レベル ÷ 100 } + 10 + レベル
それ以外 = [ { ( 種族値 × 2 + 個体値 + 努力値 ÷ 4 ) × レベル ÷ 100 } + 5 ] × 性格補正
HからSまでの各項目に対して種族値、固体値、努力値という3つの値をそれぞれ持っています。
この文章を名詞と動詞に切り分けます。
第一段階
名詞 | 動詞 | ポケモン、HP、物理攻撃力、物理防御力、特殊攻撃力、特殊防御力、スピード、種族値、固体値、努力値、性格補正 | 算出する |
---|
といったところでしょうか。一般的には、名詞はクラスまたはプロパティに、動詞はメソッドになります。HP、物理攻撃力、物理防御力、特殊攻撃力、特殊防御力、スピードは「能力値」であるということで、これらはまとめてしまいます。
第二段階
名詞 | 動詞 | ポケモン、能力値(HP、物理攻撃力、物理防御力、特殊攻撃力、特殊防御力、スピード)、種族値、固体値、努力値、性格補正 | 算出する |
---|
そして文章から種族値、固体値、努力値は能力値が持っていることが分かるので、能力値というひとつの構造が想像出来ると思います。「能力値は能力値でもHPは計算式が違うじゃん、どうすんの?」というのは後で説明するので一旦置いておいてください。また、性格補正についても、前回を見ていれば分かると思うので細かい説明をせずに、いきなりポケモンのプロパティにしちゃいますが、そこも一旦置いておいてください。
能力値
プロパティ |
---|
|
メソッド | 算出する |
ポケモン
プロパティ |
---|
|
という感じです。しかし、これ二個位なら良いですが10個も20個もクラスが現れるシステムになるとアレしてしまうので、これを表現するのに特化したダイアグラムの記法があります。クラス図です。クラス図を書くためのツールはいくつもあるのですが、個人的にはMSのVisio(Windows)が最強だと思っています。VisioはMSの三大功績の一つです。三大功績の全ては以下です。
- Visio
- VisualStudio
- 万人にインターネットを繋がるようにしたこと
しかし、たった一つ、そうたった一つIEのアイコンに「インターネット」とラベリングした罪だけは……
それは置いておいて今回はクラス図を描くツールとしてCacooを利用します。
クラス図
こんな感じになりました。クラス図の読み方ですが大体見た通りです。ボックスがクラスで線が関連を表現しています。一番上の箱がクラス名、次の段がプロパティ(変数)、その次の段がメソッド(関数)です。実際のプログラムではゲッターとかセッターとか必要なはずですが、ここでは概念レベルで充分なので省略しています。関連線の端っこにもなんか記号が付いてますが、これも関連の有り様を表現しています。
- △
- 汎化とか言います。継承を表現します。
- ◇
- 集約と言います。hasの関係を指します。このクラス図には出てきていません。
- ◆
- コンポジションと言います。それ無しでは存在出来ず、ライフサイクルを共にする関係を指します。要するに集約先のクラスの部分でしかない関係を表現します。
他にも詳しいルールとか有るし、これだけでは充分ではないですが、自身のイメージを書き下して整理する分については、これだけ分かっていれば始められると思います。詳しい読み方やルールについては特化しているサイトとか有るので、興味を持った方は、そういうところで勉強してください。UMLも万能のツールではないし、そこに対して原理主義的になり過ぎると宇宙の成り立ちからモデリングし始めるOOP怖いの世界に突入しがちなので用法・容量を守ってご利用ください。個人的にはUMLに関する本を一冊くらい読んでおくのも良いと思います。クラス図、アクティビティ図、シーケンス図は書けるようにしておくと色々楽になると思います。
性格について
Pokemon, Parameter, Hitpointは良いとして、Temperamentってなんだよ?って話だと思います。これは性格です。性格補正のための性格を表現しています。名詞と動詞の分類の時には性格補正という形でしか出てきませんでしたが、前回もチロっと触れていたのでなんとなく分かってもらえると思います。ちょっと実装が特殊な形になる予定なので詳しい説明は、そちらに回します。ソースを既に見ている方は、あーなるほどねってなっているのではないでしょうか?
継承について
Parameterを継承してHitpointというクラスが定義されていますね。概念レベルで語れば抽象度を一段階下げたモノを作ることです。魔法少女まどかマギカ的に言えば、普通のまどかがサブクラスでアルティメットまどかがスーパークラスです。分かる人以外には全然分からないですね。つまり、人間がサブクラスで動物がスーパークラスです。はい、そういう考え方を始めると、全てのクラスのスーパークラスにGenesisとかHolonとか置き始めるアレな世界観になってしまうので身も蓋も無い言い方をします。クラスを拡張したり上書きして別のクラスを作ることです。ソースコードレベルで言えば、以下のようなことを自動的にやってくれるってことですね。
元クラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php class xyzzy { private $hoge = null; public function setHoge ($hoge) { $this->hoge = $hoge; } public function getHoge () { return $this->hoge; } public function helloHoge () { print 'Hello, ' . $this->hoge; } } |
その先のクラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php class xyzzy2 { private $hoge = null; public function setHoge ($hoge) { $this->hoge = $hoge; } public function getHoge () { return $this->hoge; } public function helloHoge () { print 'Hello, ' . $this->hoge; } // // これが追加した分のコード // public function helloFuga () { print 'Fuga, ' . $this->hoge; } } |
継承を用いた場合
1 2 3 4 5 6 7 8 9 10 |
<?php class xyzzy2 extends xyzzy { // // これが追加した分のコード // public function helloFuga () { print 'Fuga, ' . $this->getHoge(); } } |
じゃあ、ややこしいしコピペで良いじゃん!っていう話でもあるのですが、じゃあ、コピペ元のソースを直したら忘れずに両方直すことを誓えますか?っていうことでもあります。PHPでは型が緩いのでアレですが型という制約の中でプログラムを始めると、もっと重要な意味を持ち始めます。これは説明をするより、実際に体験してみてもらった方が分かり易いので、是非Java等の型が重要な意味を持つ言語にもトライしてみてください。さて、元のクラス図に戻ります。Parameterを継承しているにも関わらず、Hitopointでもcalcを実装していました。どういうことでしょう?これはオーバーライドを示します。HitpointはParameterの一部ではあり、同じ属性を持ち外部から見た時には同じように振舞いますが計算式が違うので内部の振舞を上書きをする…… ということです。なかなかピンとこないかも知れませんが、次回、ソースを実際に説明する時に見てみれば「なーんだ」ってことになると思います。「なーんだ」と言えるのを楽しみにしていてください。
まとめ
ポケモンに関するクラス構造を考えつつ、オブジェクト指向そのものを説明をするというチャレンジングな回になってしまいました。内容盛り沢山ではありましたが頭の中の整理は付きましたか?次回は
- 前提条件となるポケモンのステータスの仕様を説明する
- 仕様に合わせてベースとなるクラス構造を考える
- メジャーどころを抜くためのS振り調整計算機を作る <= ココ
- 不利な相手を想定した耐久調整用計算機を作る
ですね。是非次回も読んでください。アライドアーキテクツではポケモントレーナーと初めはオブジェクト指向そのものについては触れないで全部を置いてけぼりにする宣言をした癖に蓋を開けてみれば、置いてけぼりにすると寂しいのでついついクドクドと説明してしまう寂しがりのエンジニアを募集しています
なるべく他のメンバーは書かないであろう流行りとは程遠いところや、 枯れたキーワードについて書いていこうと思います。 Perl / Java / PHP 及び周辺技術に興味があります。