■ はじめに: 「なんで必要なの?」という気持ちの行先
プログラム入門記事って沢山ありますよね。
しかし、「なんで必要なの?」 という所に目を向けた記事って案外少ないように思います。
「クラス」や「インスタンス」などの概念って、スパッと疑問を持たずに理解できた人ってどれくらい居るんでしょうか。
私は初心者のころ何度も戦っては破れ、戦っては破れ・・・ 心折れそうになりながら概念を習得していきました。
もちろん色々な本、記事を読みました。先輩エンジニアに聞いたこともあります。
でもぜーーーーーーんぜん、分かりませんでした(厳密には今も分からないのかも)。
何なんですか『犬と猫に「鳴け」と指示すると、ワンとニャンと鳴きます』って。
いや言ってることは分かるんですけど、分からないんですよ。分かんない。
で、それってつまりなんなの...? っていう感じ。
必要性が感じられないし、よく分からないけど皆必要だって言ってる・・・だと、学ぶのって辛いです。
新入社員の方々も入社されてからそろそろOJT等始まっている季節だと思いますし、
そういった方に向けた、「クラスって何?なんで要るの?」というところに目を向けた記事 にチャレンジしてみようと思います。
本記事では、プログラム言語は C# を使いますが 、あまり言語に依存しません。量も少なめです。
あくまでも言葉をメインにして、皆さんの理解しやすいであろう ゲームを題材 に、ひとつずつステップを踏み、
それがどんなものであるか、何故必要なのか。その理由を考えながら進めていきます。
■ 前段: 「名前」と「場所」がクソゲーを作る。
みなさん、「強制エンカウント」 という言葉をご存知でしょうか?
RPGゲームにおいて、どうやっても回避不能の戦闘に巻き込まれる、という意味を持つ言葉です。
何故いきなりこんな話をしているのかと言うと、ただ何も考えずにプログラムを書いてしまうと、
知らず知らずのうちに「名前」や「場所」に起因する、「強制エンカウントの推理ゲーム」という望まぬクソゲー をプログラムの中に埋め込んでしまうからなんです。
今回の記事で取り上げる**「クラス」や「インスタンス」、「メソッド」** は、あなたがクソゲーを作り上げてしまうことを回避するための手段です。
そして本記事では、この**「名前」や「言葉」**に着目して解説をしていきます。
まだ**『何言ってるんだお前。』**という感じだと思いますので、ここからひとつずつ解き明かしていこうと思います。
■ 「クラス」は、ゲームにおける「ジョブ」と一緒だ。
あの良く分からない**「クラス」は、ゲームにおける「ジョブ」と同じだ**、と考えるとイメージがしやすいのではないかと思います。
少しだけ説明を先取りすると、
例えば、
- 戦士
- 魔法使い
- シーフ
- 遊び人
- ナイト
という「ジョブ」があれば、どれも その「名前」を見ただけで、何ができるのか、どんなものなのか想像できる でしょう。
そしてそれこそが**「私たちが物や概念に「名前」を付ける理由」** であり、「クラスを作りたいという一番のモチベーション」 につながるものなのです。
この時点ではつまりどういうこと? という事についてはまだ説明しません。
ふ~ん、位の気持ちでいてください。
説明を先取りしてしまったため少し順番があべこべとなりますが、
このあと、まずは改めて「ジョブ」について説明をしていきます。
□ ジョブ is 概念に名前を付けたもの。
皆さん、ゲームをするときに 「ジョブ」 って聞いたことありませんか?
いわゆる、キャラクターの 「職業」 という奴です。
「戦士」 や 「魔法使い」 といった名前を見ただけで、何となくどんなことが出来るか想像できますよね?
- 戦士 はきっと力が強くて、剣とか斧とかを使って強い物理攻撃ができそうです。
- 魔法使い なら、杖を持っていて炎や氷で魔法攻撃ができるでしょう。
- 忍者 なら手裏剣を投げたり、忍術で分身して攻撃を回避できたりしそうです。さすが忍者汚い。
では 「魔王」 とか 「ケルベロス」 という名前を見たならどうでしょう(それジョブなの?)。
何かもう 強敵っぽい雰囲気すごい です。たぶん敵でチョー強いでしょう。おそらくボスだと思います。
このように 何かの「特徴」ある名前 を目にするとそれだけで中身が推測できるはずです。
そしてこの、
『何かの特徴を持つ「概念」に対して「名前」をつけたもの』が「ジョブ」なのです。
例えば、
- 物理攻撃で戦えるから「戦士」
- 魔法を使えるから「魔法使い」
- 忍術を使えるから「忍者」
です。
**『何かの特徴を持つ「概念」に対して「名前」をつけたもの』**なんですから、
逆に考えると、名前を見れば、そのジョブがどんな物で何ができるのかある程度想像できる はずです。
「戦士」が戦闘中に突然歌って踊りだしたりするとは思いませんよね。
「遊び人」に盾で仲間を守る役目を期待する人も居ないでしょう。
実は、これらと全く同じことがクラスにも言えます。
□ クラス is 概念に名前を付けたもの。
クラスも全く同じです。
プログラムの世界で、『何かの特徴を持つ「概念」に対して「名前」をつけたもの』が「クラス」です。
たとえば、
- 「キャラクター名」クラス
- 「ダメージ計算」クラス
- 「獲得スコアランキング」クラス
という名前のクラスを見かけたら、どんなことが出来てどんな物なのか、なんとなくイメージできますよね?
- 「キャラクター名」なら、何するかは分からないけど、「たけし」とか「しげる」みたいな名前がついていそうです。
- 「ダメージ計算」なら、キャラクターが攻撃したりされたりの際に、威力やダメージの量を計算できるっぽいです。
- 「獲得スコアランキング」なら、きっとそのゲームを遊んでいる人たちがのスコアが順位付けできるのでしょう。
ジョブと同様に、クラスも「概念に名前を付けた物」なのですから、
その名前に込められた想いをくみ取る ことで、どんな物で何ができるのか ある程度想像できます。
「キャラクター」クラスに、ゲームのオープニングの動画が入っているとは考えません。
「ユーザーID」クラスがパスワードを取り扱うなんて、誰も想像もつかない訳です。
ジョブと変わりませんよね。
そしてこの**「概念に名前を付ける」**という行為こそが、本記事の核心となります。
□ 「ジョブ」と「クラス」の たった一つの決定的な違い
ジョブとクラスは、どちらも『何かの特徴を持つ「概念」に対して「名前」をつけたもの』でした。
しかし「ジョブ」と「クラス」には 一つだけ決定的な違い があります。
それは、選ぶだけのジョブと違い、『クラスはあなたが自由に作ることが出来る』 という点です。
ジョブは、ゲームを作った人たちが用意したジョブから選ぶだけですよね。
私は魔法剣士的なジョブが大好きなんですが、そういうジョブってない事も多いんです。
あっても何故かリフレマシンにされたりとか(怨)
しかしクラスの場合は、**「お前何出来んねん」**ということを示す名前を 「あなたが」 つけてクラスを作り、
作ったクラスたちを繋げることでシステム全体を描き上げていくことになります。
魔法+剣+盾+銃+弓 を自在に使いこなすキャラクターという、僕の考えたさいきょうの魔法剣士() みたいなことをしちゃっても良いんです。
もう殴っても怒られません。
ワクワクしてきませんか?
■ なぜ名前を付けたいのか?
これまで、『何かの特徴を持つ「概念」に対して「名前」をつけたもの』 がジョブでありクラスである、というお話をしてきました。
では、そもそも何かの概念や特徴に対して 「名前を付ける」 と、どのような効果があるのでしょうか?
・・・
なんと 「人間にとって」分かりやすくなります!
それだけ です。
ですが、それこそが何よりも大事 だと私は考えています。
現実世界やある世界の中に存在する様々な物や現象に「名前」をつけてあげると、意思疎通しやすくなりますよね。
例えば 『ピカってなってゴロゴロするやつ』って言うと、万人が同じ理解をしてくれるか分かりません。
状況によって、その言葉が意味するものが変化してしまいます。
だからこそ、名前を付けるという行為はすごく大切なもの なのです。
『ピカってなってゴロゴロする』という概念 に 『雷』という名前を付けた ことで、間違いなく意思疎通できるようになりました。
名前がない時と比較すると確実により分かりやすいですよね。
名前の力、すごいです。
□ プログラムと名前とクソゲー
この名前を付けるという行為、プログラムにおいても全く同じことが言えます。超大切です。
分かりやすい名前が付いてないと、プログラムを理解するのって死ぬほど難しくなる んですよ。
だって、b
って言われてもなにができるのか1mmも分からないですよね。
プログラムを読んでいて、クラスや変数の名前が a, b, c とかになっていたら発狂ものです。
いきなりその名前が本当は何なのかという、「強制エンカウントの推理ゲーム」という望まぬクソゲー が始まってしまいます。
私達がプログラムを読むときって、本来はシステムを理解したり改修したりしたいんですよ。
推理ゲームしたい訳じゃないんですよね。
クソゲーを生み出してしまわないようにするためにも、プログラムと意思疎通しやすくしてあげる必要が生じます。
それが初めて見た人とでも、3年後の自分とでも。プログラムと「会話」出来るようにしてあげなくてはいけません。
何なら 名前を見ただけでプログラムが語りかけてくる位が理想 です。
そこで、ぱっと見でも理解しやすくるために、
プログラムで解決したい何か を、
『何かの特徴を持つ「概念」に対して「名前」をつけたもの』すなわち**「クラス」として切り出す** ことで、構造化 します。
□ クラスとは?
この**「クラス」**をもう少し細かく、かつ大雑把に説明すると、
- 「人間が理解しやすい」 ように、
- 「システムで解決したい何か」 を、
- 「小さな概念に分割して」、
- 「名前を付けたもの」
です。
システムで解決したい何か とは、システム化する業務だったり、解決したい困りごとやこんなジョブが欲しいという要望などです。
それを分析し、理解しやすい粒度に分割して、理解しやすい名前を付けてクラス とし、そのクラスを 構造化することで、
パッと見ただけで分かる形にしてあげて、強制エンカウントの推理ゲームを回避したいんです。
例えば、「装備品」クラスの中に、「頭」、「胴」、「右手」、「左手」クラスがそれぞれ入っている構造 にしたとします。
するとこの構造を見ただけで、『装備は4か所に出来るんだな』とか、『右手には武器を持ちそうだな』 とか想像できるはずです。
このように上手く設計されていれば、強制エンカウントの推理ゲーム を回避出来そうですね。
他にもクラスには出来ることも考え方も色々あるのですが、
このあたりを理解しないまま、クラスってなに? っていう説明だけを詰め込んでも分からなくなりがちです。
だって極論を言えば別に作らなくても良いんですもん。クラス。
無くてもプログラムは動くし。
ふーん、でも良く分かんないし要らないじゃんってなります(なりました)。
□ なぜ「クラス」を使って「分かりやすくする」ことが大事なのか?
もう少し詳しい例で考えてみましょう。
例えばゲームを作っていて、「毒」を食らった時の処理にバグが見つかった とします。
この時、もしも プログラムが全て void Main()
に書かれていて、しかも3万行あったとしたら。
この3万行の中から「毒」にかかわる部分を探し出すのはとても大変です。
- それはもしかしたら 自分の書いたコードではない かもしれません。
- 自分が書いたコードでも 3年後すっかり忘れてから見る ことになるかもしれません。
- 直したら2000行後に余計な影響が出て新しいバグになってしまう かもしれません。
- 全部直したつもりでも、実は5000行離れたところにも毒に関する処理が書かれている かもしれません。
・・・いずれにせよ絶望です。
そう、どこにあるか分からないものを探す作業ほど辛いものはありません。
それは拷問って言うんですよ。
仮に1つ見つかったとしても、もしかしたら他にもあるかもしれない・・・ と、埋まっているかどうか分からないものを探す ことになります。
で、全部探してきちんと直したはずなのに、なぜかまだ治ってなかったりします(遠い目)
ちなみにそのようなコードを取り扱うことは、「地雷処理」 とも呼ばれています。
全てが疑心暗鬼。ほんとつらい。
でも、もしも適切な粒度にクラスが分割されていて、「毒クラス」や「毒状態クラス」 があるとしたら。
毒に関して知りたければこのクラスを見ればいいんです。それが名前を見ただけで分かります。
また「毒クラス」から使われているクラスを順番に見ていけば、修正したときに影響が起きてしまいそうな部分も分かります。
ぜーーーんぶを疑いのまなざしで見なくてはいけない状態と比較すると、随分 「楽」 ですよね。
積読本が平積みされている私の部屋と、あいうえお順に整理された図書館くらいの差があります。
(まだ 30cm くらいしか積んでませんよ)
例え地雷が埋まっていても、埋まっているところに目印があれば、それは**「地雷処理」** から**「爆弾処理」** に変わるのです。
そりゃ爆弾も怖いですよ。でも どこにあるかさえ分かっていれば、そこだけに注意を向けることが出来ます。
もうびくびくしながら歩かなくてもいいようにするために、「人間にとって」分かりやすく構造化 をして、地図を作りあげることが大切なのです。
埋まっている場所さえ分かっていれば、笑顔で鼻歌交じりに歩くことだって出来ます。
その手段の一つが「クラス」なのです。
繰り返します。
- 「人間が理解しやすい」 ように、
- 「システムで解決したい何か」 を、
- 「小さな概念に分割して」、
- 「名前を付けたもの」
がクラスです。
まずはここだけでいいので、抑えてください。
もっと細かな考え方も山ほどあるのですが、そういう細かなことについては後々覚えていけば OK です。
難しい問題でも、問題そのものを 「小さな概念に分割」 してあげれば、私みたいなよわよわエンジニアでも理解できる程度の難易度に収まります。
3万行の void Main()
は理解できませんが、適切に整理された100行のクラスが300個なら理解できるのです。
クラスが300個あっても、その関係がツリー状に整理されていれば案外理解できるものですよ。
必要なものを必要な時にだけ見れば良いんですから。
同時に見なくてはいけないクラスなんて、その1/10にも満たない でしょう。
■ では実際のところ、どうやって切り分ける?
先ほどから 「システムで解決したい何か」を「小さな概念に分割して」 クラスを作ると書いています。
しかし例えば**「キャラクター」**と一口に言っても、ゲームの キャラクターを構成しうるもの って無限大にあります。
このため、その 切り分け方も無限大 です。
様々な切り口がありますが、どれが合っていてどれが間違っているかというと、別にどれも間違っていません。
例えばアクションゲームとRPGゲームでは同じ「キャラクター」でもそれぞれに求められる要素は異なるでしょう。
同じRPGゲームというジャンル内でも、ファイナルな感じのやつとドラゴン的な奴では求められる要素が全然違うわけです。
たった一つの正解はありません。
すなわち、今回作りたいシステムの世界にとって「必要な要素」 を、どう切り出してどのように組み合わせていくとよい のか、その答えは無限大 にあるということです。
だからこそ「設計」が大切なのであり、そこがあなたの腕の見せ所になるのです。
・・・ということでご察しの通り、「どうやって切り分ける?」という問いに対する明確な答えは残念ながらありません。
あくまでも**「あなたのシステムにとって必要な粒度」**で切り分けていきましょう、という月並みな回答になってしまいます。
最終的には、いわゆる 『SOLID 原則』や『DDD』 に則った考え方や構造を目指すと良いでしょう。
しかしそれだけで記事が数十本書けるほど難解なので、その詳細については今は説明しません。
まずは、
- 「システムで解決したいこと」を反映した概念を、理解しやすい粒度で切り出す (今回の課題で必要最小限の、分かりやすいクラスたちを作る)
- 1つのクラスには役割を1つだけにする
- クラスの大きさを最大100行程度とする
という所から始めると良いでしょう。
切り出した概念を組み合わせて、概念の階層構造を作る(クラスの中にクラスを含めて親子関係を作る) となお Good です。
全ては、ひとつひとつのクラスをシンプルに保つため です。
1万行のクラスを作ってしまったら、やっぱり理解不能になってしまいますから。
なお階層構造をつけると、このようなイメージになります。
キャラクタークラスの一要素としてステータスクラスがあって、
ステータスクラスの中には名前などの値と、HPクラス、MPクラス、能力値クラスがあって・・・といった形で、
うまく概念を切り出し、その親子関係を表現することが出来れば、理解も修正も簡単に出来るようになります。
『キャラクターのステータスにある能力値』 と考えれば、自然とその関係性が理解できるはずです。
これでもう、void Main()
の 13256行目にある if 文を直さなくても良くなりました。
キャラクターについて知りたければキャラクタークラスを、HP に関する処理を知りたければ HPクラスを見ればよいのです。
もう、粒度の違うものがたくさん並んでいて混乱することありません。
長々と説明をしてしまいましたが、クラスが、
- 「人間が理解しやすい」 ように、
- 「システムで解決したい何か」 を、
- 「小さな概念に分割して」、
- 「名前を付けたもの」
である、という意味がちょっぴり理解できた気がするでしょうか。
クラスを長い処理を分割するために使ってはいけませんよ?
あくまでも「概念を分割する」ために使うのがコツ です。
難しい問題は小分けにして考える、というあらゆる問題解決に共通する手法ですね。
分かりやすい構造を作れば、見るべき場所がすぐに分かるので、推理ゲームを強いられることもなくなります。
さて、次は分からないポイントその2である**「インスタンス」**について考えましょう。
#■ 「インスタンス」って?
「クラス」を「インスタンス化」すると、「インスタンス」ができます。
何を言っているのか良く分かりませんね。
英語の「インスタンス」は「実例」という意味ですから直訳すると、
「クラス」を「実例化」すると、「実例」ができます。
になります。
・・・?
やっぱりあまり意味が分かりませんね。
ゲームで言い換えましょう。
「インスタンス化」とは、ゲームにおける「キャラクターの作成」です。
自由なキャラクターを作ることのできる RPG ゲームを想像してください。
好きな 「ジョブ」を選んで、 必要な情報を入力 して キャラクターを「作成」 します。
例えば...
- 「ジョブ: 戦士」を「たけしと名付けて作成」すると、「戦士 たけし」ができます。
- 「ジョブ: 魔法使い」を「しげると名付けて作成」すると、「魔法使い しげる」ができます。
- 「ジョブ: 魔法使い」を「みよこと名付けて作成」すると、「魔法使い みよこ」ができます。
このように「ジョブ」を選んで、名前をつけて「作る」ことで、初めて**「キャラクター」の実物** が出来上がります。
ふわっとしたジョブの定義と、そのジョブとして生まれたキャラクター、という関係性です。
あくまでも 「ジョブ」はキャラクターの特徴や概念を表したもの でしかありません。
例えば「戦士」であれば、『力が強くて斧を振って戦う人だよね』という特徴や概念を表したものになります。
また例え同じ「魔法使い」という概念であっても、
キャラクターを作成するときに、異なる情報(得意属性や能力値の割り振り) を入力すれば、
出来上がったキャラクターたちは名前やヒットポイントや攻撃力、使える魔法などが異なる でしょう。
このように同じ概念でも、複数の異なるもの を取り扱うことが可能になります。
この 「ジョブ」と「キャラクター」の関係性と同じ なのが、「クラス」と「インスタンス」 です。
- 何か特徴のある「ジョブ」を選んで、必要な情報を入力して、キャラクターを作る。
- 何か特徴のある「クラス」を選んで、必要な情報を入力して、インスタンスを作る。
言葉が入れ替わっただけで全く同じですね。
何かの特徴ある名前を持つ「クラス」という概念 から 実物を「作る」(インスタンス化する) ことで、
「そのクラスの特徴を持った、実体(インスタンス)」 が出来上がります。
分かりにくいので言い換えてみます。
インスタンスは「クラスと言うふわっとした概念を実体化したもの」 です。
例えば**「スライム」という概念(クラス)** があるとき、
その概念を 実体化(インスタンス化)する と、『スライムA』 のような実体(インスタンス) が出来上がります。
ゲームの戦闘で スライムが3体出現した場合、「スライム」クラスのインスタンスが3個 出来上がるでしょう。
いよいよ意味が分からなくなってきたので、そろそろコードを交えて考えてみましょう。
□ まずは「クラス」と、その「インスタンス」を使わないとどうなるか(ダメな例)
そもそも、なぜ「キャラクター」クラスの「インスタンス」を作る必要があるのでしょう?
それは、「たけし」、「しげる」、「みよこ」という キャラクターを、プログラム中で『人間が』扱いやすくするため です。
そう、「扱いやすくするため」なので、極論を言えばクラスなんて作らなくてもいい んですよ?
試しにキャラクターを3人分、クラスを使わないでプログラムに書いてみましょう。
public static void Main() {
string character1 = "たけし";
string character2 = "しげる";
string character3 = "みよこ";
}
こんな感じです。
これならクラスなしでもいけそうですね。
でもこれって、3人しか登場人物が居ないからなんとかなっているんですよ。
もしも100人になったらどうなるでしょう?
public static void Main() {
string character1 = "たけし";
string character2 = "しげる";
string character3 = "みよこ";
string character4 = "たろう";
string character5 = "しむら";
string character6 = "まきせ";
// ...
string character81 = "どうばやし";
string character82 = "いちおか";
// ...
string character98 = "あらい";
string character99 = "まえだ";
string character100 = "くろだ";
}
こうなります。
今はまだ名前しかないからギリッッギリなんとかなります。
名前だけあってもゲームにはなりませんから、ヒットポイントやマジックポイント、攻撃力などもセットで取り扱わないといけないでしょう。
すると...
public static void Main() {
string nameOfcharacter1 = "たけし";
int hpOfcharacter1 = 500;
int mpOfcharacter1 = 50;
int attackOfcharacter1 = 100;
string nameOfcharacter2 = "しげる";
int hpOfcharacter2 = 200;
int mpOfcharacter2 = 122;
int attackOfcharacter2 = 250;
string nameOfcharacter3 = "みよこ";
int hpOfcharacter3 = 35;
int mpOfcharacter3 = 999;
int attackOfcharacter3 = 9999;
// あと97人分...
}
こうなります。
名前とヒットポイント、マジックポイント、攻撃力しかありませんが、3人ですらこんな惨状です。
ここからこれらのキャラクターの情報を使って戦闘をさせていくわけです。
100人も居たらとてもじゃないですが管理しきれませんよね。
また100人頑張って作った後に、機能追加で職業や防御力、素早さが増えたらどうなるでしょう。
あるいは偉い人が『仲間1000人になったから明日までにヨロシク^^』って言ってきたら。
絶望です。
1番目の「しげる」が68番目の「きくち」を攻撃しようとしたとき、
番号をひとつ間違えて69番目の「たなか」を攻撃してしまう。
でもよく見てみたら61番目の「まつやま」のヒットポイントが減っていた... なんてミスが起こる0.2秒前です。
コピペしやすいししたくなる作りですからね。
笑い事ではなくてフツーにこんなミスが起きます。
もはや管理不能です。どうやっても不具合が出ます。
□ では、そもそも何故管理しきれなくなってしまうのでしょうか。
それは**「キャラクター」という概念を表す**には、いくつもの概念(値) をセットで扱わなくてはいけないから です。
その値をプログラムにべた書きしていくと、概念に必要な個数分の変数が爆誕してしまいます。
このため何も工夫せずに書いていくと、先の例のように行数が爆発してとんでもないことになりますし、
常にセットで取り扱う必要があるので、ちょっとしたミスで値を取り違えたり使い忘れるなどしてバグを生んでしまうのです。
そこで登場するのが先ほどから何回も連呼している、
- 「人間が理解しやすい」 ように、
- 「システムで解決したい何か」 を、
- 「小さな概念に分割して」、
- 「名前を付けたもの」
つまりクラスです。
先ほどの実例に沿って言葉を置き換えると、
- 「人間が理解しやすい」 ように、
- 「ゲームの登場人物」 を、
- **「キャラクターと名前、ヒットポイント、攻撃力という4つの小さな概念に分割して構造化」**し、
- **「名前を付けてクラス化」**した。
という形になります。
ここでは一例として、キャラクターの一要素として、名前、ヒットポイント、攻撃力を持たせました。
こうやってキャラクターを構成する要素が1か所にまとまっていれば、他人のステータスを間違えて使ってしまうミスなんて起こり得ない のです。
□ 実際に「キャラクター」クラスとその「インスタンス」を作ってみる。
では実際にプログラムにしてみましょう。
名前、ヒットポイント、マジックポイント、攻撃力 の4つのステータスを持った、キャラクタークラスを作ってみます。(先ほどの例からマジックポイントが増えました)
C# のコードだとこんなイメージになります。
// キャラクターという特徴を持ったクラスを作る。
// その特徴は、キャラクター名と、ヒットポイント、マジックポイント、攻撃力を持っていること!
public class キャラクター {
private string _キャラクター名;
private int _ヒットポイント;
private int _マジックポイント;
private int _攻撃力;
// このクラスのインスタンスを作るとき、文字や数値をくっつけられるようにします。
public キャラクター(string name, int hp, int mp, int attack) {
_キャラクター名 = name;
_ヒットポイント = hp;
_マジックポイント= mp;
_攻撃力 = attack;
}
}
そしてこの 「キャラクター」クラス を、『たけし, HP=500, MP=50, Attack=100』という内容で作る(インスタンス化する) と、
『たけし』という名前を持った、キャラクタークラスの実体(インスタンス) が出来上がります。
// こんな感じで使う
public static void Main() {
// たけし、しげる、みよこ を新しく作るとき、4行必要だったのにたった1行で作れるようになりました。しかも1か所にまとまります。
キャラクター たけし = new キャラクター("たけし", 500, 50, 100); // HP=500, MP=50, Attack=100
キャラクター しげる = new キャラクター("しげる", 200, 120, 250); // HP=200, MP=120, Attack=250
キャラクター みよこ = new キャラクター("みよこ", 25, 999, 9999); // HP=25, MP=999, Attack=9999
}
この左側にある「たけし、しげる、みよこ」がインスタンス(実体) です。
どうでしょうか?
これなら 100人いても何となく管理できそう ですね。
えらい人に『防御力と素早さが追加になっちゃった(∀`*ゞ)テヘッ』 って言われても、
new キャラクター("たけし", 500, 50, 100, 60, 81);
のようになるだけで行は増えません。
今度は 500, 50, 100, 60, 81 の順番を間違えて失敗する可能性がありますが、
少なくとも 何百行と必要になっていたものたちを、小さくまとめられる ようになりました。
このように、システムを作りたい世界において 「特徴を持つ概念」 を分析し見つけて定義・設計し、色々なクラスを作っていくのです。
作ったクラスをインスタンス化することで、「ひとまとめにしておきたいもの」 を扱うことがとても楽になります。
もう、
string nameOfcharacter1 = "たけし";
int hpOfcharacter1 = 500;
int mpOfcharacter1 = 50;
int attackOfcharacter1 = 100;
のように、暗黙的にセットで取り扱わなくてはいけない値 を、神経質になりながら取り扱ってあげる必要がなくなりました。
それらの値は全て「キャラクタークラスのインスタンスの中」に入っています。
ひとまとめで取り扱えるので、理解もしやすくなります。
複数行に分かれてしまっている値をすべて頭におきつつ読み進めるよりも格段に楽ですからね。
※ここでは詳しく説明しませんが、値を間違えて指定してしまうバグへの対策として、ValueObject(値オブジェクト) という考え方があります。
手前味噌ですが、もしも気になった時は下記の記事をご確認ください、
記事: 『DDD に入門するなら、まずは ValueObject だけでもいいんじゃない?』
■ じゃあ「メソッド」って何?
欲張りにもう一歩だけ軽く踏み込みましょう。
「メソッド」 について考えてみます。
よくある説明は、『そのクラスの振る舞い』 だとか、『走る』 とか 『鳴く』 とかです。
いやー... 分かるけど分からない。
直訳ともほかの説明とも異なりますが、
メソッドとはそのクラスが「出来ること」
を意味します。
改めてゲームで例えてみます。
先ほどのジョブには、それぞれに 「出来ること」 があります。
- 「戦士」 は 「剣を振る」 ことができます。
- 「魔法使い」 は 「魔法を放つ」 ことができます。
- 「忍者」 は 「姿を消す」 ことができます。
このように、そのジョブに出来ること があるのですから、
「戦士 たけし」は剣を振る ことが出来ますし、「魔法使い みよこ」は魔法を放つ ことができます。
あなた戦士なんだから剣を振れるよね? という事です。
この話、クラスに置き換えても全く同じものになります。
例えば、戦士クラスに「防御する」という出来ること(メソッド) を作る ことにします。
public class 戦士 {
private string _キャラクター名;
private int _ヒットポイント;
private int _攻撃力;
public 戦士(string name, int hp, int attack) {
_キャラクター名 = name;
_ヒットポイント = hp;
_攻撃力 = attack;
}
// 「防御する」という、ダメージを50%に軽減する「出来ること(メソッド) 」を作った。
public void 防御する(int ダメージ量) {
_ヒットポイント = _ヒットポイント - (int)(ダメージ量 * 0.5);
}
}
すると、その戦士クラスから作られたインスタンスも 「防御する」 ことができます。
戦士という概念に 「防御する」 という出来ることを定義した。
だから、戦士たけしも防御できるのです。
public static void Main() {
戦士 たけし = new 戦士("たけし", 500, 100); // HP=500, Attack=100
// たけしは100ダメージを受けるところを、防御で50ダメージに軽減した
たけし.防御する(100); // HP=450
}
この関係性、なかなか理解が難しいところです。
クラスがふわっとした概念ですから、そのクラスにあるメソッド(出来ること) も概念なんです。
こんな風に防御できるよーってただ決めただけです。
インスタンス化して実物にしてあげないと、メソッドは使えません。
やはりふんわりしているので、もう一度ゲームで言い換えてみましょう。
スライムという概念(クラス) に**「武器を溶かす」という出来ること(メソッド)** があったとします。
概念ですから、そもそも空想上のふわっとした存在でしかなく、体を持たないので「武器を溶かす」という行為は実現できません。
空想上のスライムは、相手の武器を溶かそうにも体がないから出来ない のです。
でもその概念からスライムA , B, C を作った(インスタンス化した)ら、そのスライムは体を得たので武器を溶かせる訳です。
メソッドでも、概念(クラス)と実体(インスタンス)、という関係性が大切 なんですね。
□ では何故、メソッドを作りたいのでしょうか?
それは、ある概念(クラス) に紐づく処理は1箇所に纏めておきたいから です。
クラスの説明で少し触れた、「強制エンカウントの推理ゲームという望まぬクソゲー」 の事を思い出してください。
プログラムを読んでいて、クラスや変数の名前が a, b, c とかになっていたら発狂ものです。
いきなりその名前が本当は何なのかという、が始まってしまいます。
という説明で、名前がクソゲーを作る という話をしました。
でも実は名前以外にもクソゲーを作ってしまう要素があります。
地雷処理の話を少ししましたね。
そう、どこにあるか分からないものを探す作業ほど辛いものはありません。
それは拷問って言うんですよ。...
ちなみにそのようなコードを取り扱うことは、「地雷処理」 とも呼ばれています。
全てが疑心暗鬼。ほんとつらい。
場所がわからないとすっごく辛いんです。
そう、場所がクソゲーを作る のです。
キャラクターに関する処理があっちこっちにバラバラに書かれていたら、
機能追加や修正したり、調査したり・・・ このような作業が 強制エンカウントの推理ゲーム になります。
ですから「キャラクター」という 概念(クラス) に関連する処理を1箇所に纏めておきたい という気持ちが生まれます。
1箇所にまとまっていればそこだけ見れば良いのですから、3万行の中から目を皿にしてひとつずつ探し出すよりも圧倒的に楽なことはご想像頂けると思います。
ですからその手段として、クラスにメソッドを作りたいのです。
全ては 「強制エンカウントの推理ゲームという望まぬクソゲー」 を回避するための手段です。
だって楽しくないことなんてやりたくないじゃないですか。
だから設計や名前の力を借りて簡単に理解できて、簡単に直せて・・・ という構造にしたい。そういうことです。
■ まとめ。 そしてその先
クラス、インスタンス、メソッドの何となくのイメージは掴めたでしょうか?
改めてまとめます。
用語 | ゲームで言うと? | イメージ |
---|---|---|
クラス | ジョブ | 戦士、魔法使い、忍者 (の概念) |
インスタンス | キャラクター | 戦士たけし、魔法使いみよこ |
インスタンス化 | キャラクターの作成 | キャラクタークリエイト画面で、戦士たけしを作成して生み出す。 |
メソッド | そのジョブが「出来ること」 | 戦士なら斧を振れる。戦士たけしは、戦士なんだから斧を振れる。 |
またクラスとは...
- 「人間が理解しやすい」 ように、
- 「システムで解決したい何か」 を、
- 「小さな概念に分割して」、
- 「名前を付けたもの」
です。
そしていずれも 「強制エンカウントの推理ゲームという望まぬクソゲー」 を回避するための手段です。
もし理解できなくても焦らなくて大丈夫です。
正直1回で理解するのは難しい話だと思います。やたら話がふんわりしてますし。
私もお恥ずかしながら理解できるようになるまで年単位で掛かりました。
少しずつでいいです。色々な人の説明や解説を比較しながら、どんどん試して身に着けていってください。
みんな言っていることは違いますが、大切な部分だけは同じことを言っていますから、そこに着目しましょう。
またスルっと理解できたとしても、まだまだまだまだ覚えないといけない、知らなくてはいけないことは沢山あります。
クラス周りだけでも、継承やInterface、ポリモルフィズム、抽象、etc...
これらについては今回の入門記事では詳しくは触れません。
いつか機会があればこれらについてもお話しできれば、と思います。
それでも、もしかすると何かのきっかけになるかもしれないのでイメージだけは置いておこうと思います。
用語 | ゲームで言うと? | イメージ |
---|---|---|
継承 | 上位ジョブにとっての基本ジョブ。 | ナイトは戦士の上位ジョブだから、戦士の出来ることだって出来るよね。 |
ポリモルフィズム | 上位ジョブを基本ジョブとして扱う。 | ナイトとバーサーカーは戦士の上位ジョブだから、どっちも戦士として扱っちゃえ。 |
インターフェース | 現ジョブに好きな技をセットして、その技を使える人として扱う。 | 「剣を振れる」みたいな出来ることだけに注目して、戦士やナイトを「剣を振れる」人として扱っちゃえ。 |
抽象(abstract) | 基本ジョブで「出来ること」の名前だけ決めておく。 | 「たたかう」って名前にしておくから、派生したジョブで中身は好きにしてね。魔法使いならビーム撃つとか、戦士なら斧で殴るとか、そっちで決めてね。 |
staticメソッド | ジョブの概念にある、実体がなくても出来ること。 | いかにもそのジョブっぽい出来ることだし、実体なくても使えるようにしとこ。 |
(うーん、イメージ湧くかなこれ... )
以上、このようなウルトラ長文を最後まで読んでいただき、本当にありがとうございました。
本記事が何かの助けや切っ掛けになれば幸いです。