Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

情報科提督のための、艦これで理解する「オブジェクト指向」

艦これってオブジェクト指向を説明するのにかなり優れた教材なのでは!?と思ったので、艦これを用いてオブジェクト指向を説明したいと思います。内容は簡単にしているので正確には間違っている箇所があるかもしれませんが、ご容赦ください。

事の成り行き

Twitterオブジェクト指向艦これをからめたTweetをしたら結構RTされたので、せっかくなのでより詳しく分かりやすくオブジェクト指向を説明してみたいと思ったのでブログに書くことにしました。

解説

では早速解説していきたいと思います。

クラスとインスタンス --- 設計図と艦娘

オブジェクト指向を語る上で避けられないのがクラスインスタンスです。ここでつまずく人も多いのではないでしょうか。

直感的にざっくり説明したほうが理解しやすくなると思うので先に艦これで例えてしまうと、金剛型というのはクラスで、比叡金剛型クラスのインスタンスです。金剛や榛名、霧島も同じく「金剛型」クラスのインスタンスです。

クラスとインスタンスとの違いは、クラスはそれ自体は実体を持たないという事と、インスタンスはクラスから生成された実体であるという事で説明できます。「金剛型」という艦娘は存在しませんが、金剛型の金剛という艦娘は存在します。クラスを「設計図」になぞらえる理由が、なんとなく分かってきましたか?

金剛型と同じく、「木曾」「球磨」は「球磨型」クラスのインスタンスだと考えることができますし、「最上」は「最上型」クラスのインスタンスと考えることができます。

ここで注意すべきは、クラスである「金剛型」とインスタンスである「金剛」は別の概念であり、混同してはいけないという事です。金剛は金剛型の一番艦ですが、金剛は艦娘であり、金剛型はあくまで金剛を始めとする「ある種の高速戦艦」の分類に過ぎません。

インスタンス変数とクラス変数

艦娘は、それぞれ残りの弾薬や燃料の量を把握しています。これは変数として考えることができそうです。これらの変数は各艦娘ごとに異なり、インスタンスごとに独立しています。インスタンスが独立して持つ変数をインスタンス変数と呼びます。

ところで「金剛型」という括りでも変数を考えることができそうです。金剛型排水量や積載量、速力は、全ての金剛型姉妹に共通しています。クラスに共通した変数をクラス変数と呼びます。

ただし、クラス変数はインスタンスから参照・変更することができ、変更されると全てのインスタンス上のクラス変数に変更が反映されます。これは艦これでは説明できません。

スーパークラスとサブクラス --- 戦艦::高速戦艦::金剛型

さて、クラスとインスタンスの概念が理解できたでしょうか。ここからはスーパークラスサブクラスという概念を紹介します。

簡単のために今回も艦これで例えてみましょう。さきほど「『金剛型』は『高速戦艦の分類』の一つ」と説明しましたが、「高速戦艦*1」もクラスとして考えることができそうです。いくつか例を出してみます。

金剛、シャルンホルスト、インフレキシブルはどれも高速戦艦です。つまりこれらの艦娘(金剛以外は実装されていませんが、話の都合上艦娘という事にします)は「高速戦艦」クラスに属しているという風に考えることができます。

クラスに属するクラス

さて、金剛が金剛型クラスのインスタンスであるように、シャルンホルストやインフレキシブルも直接の親となる「何らかのクラス」のインスタンスであるはずです。シャルンホルストシャルンホルスト級の戦艦であり、インフレキシブルはインヴィンシブル級の戦艦です。これらをまとめると以下のようになります。

このような場合、金剛型をはじめとするクラスとこれらをまとめる「高速戦艦」クラスは次のような階層になっていることが分かると思います。

f:id:Windymelt:20150415205250g:plain

図からも分かるように、「高速戦艦」クラスは「戦艦」クラスの下位にあり、そして「金剛型」などの上位にあります。このとき、「高速戦艦」クラスは「戦艦」クラスの特徴を、そして「金剛型」クラスは「高速戦艦」クラスの特徴を、あたかも親から子へと特徴が遺伝するように引き継いでいます。具体的には、高速戦艦は「大口径の砲を持つ」という戦艦クラスの特徴を引き継ぎ、さらに金剛型は「速力27.5ノット」という要素を高速戦艦クラスの特徴に追加しています。

このようにクラス間に親子のような形質の受け継ぎの関係があるとき、あるクラスから見て親となるクラスをスーパークラス、あるクラスから見て子となるクラスをサブクラスと呼びます。このようなクラスの親子関係があるとき、「『高速戦艦クラス』は『戦艦』クラスを継承している」という風に、「(クラス)は(スーパークラス)を継承している」と表現します。専門的な用語を使うと、このようなクラスとスーパークラスとの関係をis-a関係と呼びます*2

f:id:Windymelt:20150415211713p:plain

ちなみに「拡張する」(継承する)ことを英語では"extend"という動詞で表現します。JavaScalaでクラスの継承を表現する際に使う"extends"は、この事を意味しているのです。

抽象クラス --- インスタンスを作れないクラス

さて、クラスからインスタンスが作られるという話はすでに説明しました。ここまでの説明が理解できていれば、金剛や榛名たちは「金剛型」というクラスのインスタンスであることが分かると思います。

では、「高速戦艦」や「戦艦」クラスはインスタンスを作ることができるでしょうか?これも艦これになぞらえて考えてみると分かるのですが、どの高速戦艦に属する艦娘も、「金剛型」といったクラスに属しており、「高速戦艦」や「戦艦」といったクラスとは直接の結び付きがありません。他の例を挙げるならば、「電」「響」はいずれも「駆逐艦」ですが、より具体的には「暁型駆逐艦」というクラスの艦娘です。どうやら、クラスには直接インスタンスを生成できるものと生成できないものがあるようです。

これらの「インスタンスを作れるクラス」と「インスタンスを作れないクラス」には、それぞれ具象クラス抽象クラスという名前があり、区別されています。抽象クラスは別のクラスに継承されてインスタンスを作ることを意図しており、クラスの共通した部分を整理することができるというメリットを持っています。艦これで言うならば、暁型や睦月型などに共通する性質を「駆逐艦」というクラスで表現することで、暁型と睦月型が一種の仲間であることが分かるとともに、「暁型でも睦月型でも、駆逐艦ならどの型でも良い」という状況を、わざわざ全ての駆逐艦の型を書かずに表現することができます。抽象クラスが無ければ、遠征任務の条件を書き表わすのはとても大変になるでしょう*3

具体的な仕様は継承したクラスが決めることにして、おおまかな共通の仕組みを決めるものが抽象クラスです。抽象クラスは、クラスをまとめて扱う際に威力を発揮します。

カプセル化

突然ですがリアル軍艦の話をします。提督諸氏はリアル軍艦の指揮経験がありますか?私は無いです。

さて、軍艦は巨大で複雑な機械ですから一人やそこらで動かすことはできません。様々な人が役割を分担して働いています。この役割分担があるからこそ、超巨大な軍艦は戦闘できるのです。そしてこの役割分担は軍艦の集合である戦隊とか艦隊といった規模でも行われており、艦隊長官は艦隊の各指揮官に指示を出し、各指揮官は独自の判断で部下に指令を発していきます。このとき命令する側はその先でどのような指揮が行われるかには関与せず、与えられた自分の役割に集中します。つまり「細かい事は部下に任せる」という方針です。現場の部下のほうがより現場に精通しているので、偉い人が直々に指揮するよりも柔軟で効率的な行動ができるようになります。艦長が「攻撃しろ」と言えば、後の事は砲術長が勝手に射手とか旋回手に命令してくれます。艦長は戦況把握と命令に集中できるわけです。また、失敗があった場合の責任の所在も明確にすることができます。

命令を発する上官から見ると、部下の挙動はまるで観測も予測も不可能なブラックボックスのように見えます。それでもうまくいくのです*4。もっと言うと、部下のやり方にいちいち干渉する上司はダメな上司です。

オブジェクト指向プログラミングにおいても、この種の「ブラックボックス化」が行われます。オブジェクトは外に開かれたメソッドを通じてのみ操作することができ、それ以外の方法ではオブジェクトの中に干渉できないという原則です。このような一見不便な制約を付けることで、嫌でも処理の内容を綺麗に分離して抽象的に書くようになりますから、半ば強制的に、上下関係が整理されたメンテナンスしやすい設計を作らせているのです。この事をカプセル化と呼び、オブジェクト指向の大事な概念として扱っています。

艦これで例えてみましょう。たいていの艦娘は砲を装備していますが、どれも同じ砲ではありません。それぞれの砲は異なる操作が必要で、クセも異なるでしょう。仮に、アニメ艦これで吹雪が実践してみせたように、旗艦が他の艦娘に指示を出すとします。このとき旗艦は指揮下の艦娘がどういった装備をしているかは知る必要が無く、どこに攻撃をすればいいか、どこに脅威があるのかを教示するだけで、各艦娘は自律して行動し、敵を撃破します。

アニメ艦これ第5話、吹雪が第五遊撃部隊に指示するシーンでの会話を振り返ってみましょう。

(大井)じゃあどうしろというの?
(吹雪)瑞鶴さんと加賀さんは、まず索敵を!
(瑞鶴)索敵!?たった数隻の敵に?
(加賀)私まで?
(吹雪)だからこそ、ちゃんとした方がいいと思うんです!
(加賀)…分かったわ
(吹雪)大井さんと北上さんは、左舷雷撃戦の用意を!(同時に瑞鶴・加賀が索敵機を射出)
(北上)そうだね、分かった 
(大井)北上さん…でも誰が前に出るの?
(吹雪)私が行きます! 私が引き付けますから、みんなで攻撃を!

このシーンにおいて、吹雪は他の艦娘に、「○○型の艦載機を何機発艦させよ」「○○式魚雷を装填」といった具体的な行動を命令するのではなく、「索敵」「雷撃戦の用意」という抽象的な命令を与えています。吹雪は他の艦娘が「どのような装備を搭載しているのか」「装備を動作させるにはどのような操作をすればよいのか」に関知することなく、柔軟な命令を出してその後の行動を彼女らに任せることで効率的な行動を行おうとしています。実際に、吹雪が加賀・瑞鶴に命令した直後に彼女らは艦載機を発艦させており、吹雪は大井・北上への行動指示に集中することができています。

オブジェクトの「独立」

この「内部の物事に干渉しない」考え方は、オブジェクト指向においてはカプセル化という考え方で徹底されています。オブジェクトの中にある変数はオブジェクトの中からのみ操作することができ、オブジェクトに何らかの指示を出す際はパブリックメソッドなどの、特別にオブジェクトの外に開かれた操作手段のみを利用することで、オブジェクトを一つの完成・独立した個体として考えることができるようになります。

オブジェクトが一つの個体として独立するとどういったメリットがあるでしょうか。オブジェクトが独立しているということは、そのオブジェクトは別の場所に移動しても動作することができるということです。別の場所で動作できるという特長は、テストが簡単になるなどのメリットを得ることができます。またオブジェクトが独立していることで、外部からの意図しないバグの混入を防ぐことができるのです。

艦これで例えると、もし加賀・瑞鶴が赤城・翔鶴、もしくは最上・三隈に変わっても、カプセル化が守られていれば、同じ「索敵せよ」という指示で吹雪は二人に艦載機を発進させることができます*5。守られていなければ、吹雪は空母の装備をわざわざ確認して異なる具体的指示を出さなければならないかもしれません。

f:id:Windymelt:20150416201609p:plain

オブジェクト指向では、オブジェクトが外部に公開しているメソッドパブリックメソッド、外部に公開せずにそのオブジェクトの中でのみ使うことを想定しているメソッドプライベートメソッドと呼びます。

同じように、外部に公開して自由な値の出し入れを許可している変数をパブリック変数、内部からのみアクセスできる変数をプライベート変数と呼びます。Javaでは通例、パブリック変数に代入しようとする値が正しいものであるかを検査するために、変数に値を格納する専門のメソッドを用意し、そちらを利用させます。このメソッドセッターと呼びます。同じく、値を取り出す専門のメソッドゲッターと呼びます。この仕組みも、オブジェクトの独立を維持するためのものです。この仕組みが無ければ、パブリック変数に代入する値の検査はオブジェクトの外で代入する側が行うこととなり、オブジェクトの独立が崩れてしまいます。

艦これで例えると、艦娘が搭載する弾薬の規格や燃料の規格について、受け入れるか拒否するかを艦娘が判断します。艦娘に判断を任せることができるので、整備員(?)は全ての艦娘の弾薬や燃料の規格を逐一覚えずにすみます。規格に変更があったときでも、全ての整備員に変更を通達することなく、艦娘一人に変更を教えればよいというわけです。カプセル化は後からの変更に強いのです。

まとめ

いかがだったでしょうか。ちょっと説明が下手な所もあったかもしれませんが、僕が理解している範囲でオブジェクト指向艦これで表現するとおおむね以上のような感じになります。まとめると、

  • 概念としてのクラスから実体としてのインスタンスができる
  • クラスには親子関係があり、子は親の形質を引き継ぐ
  • インスタンスを作れないクラスもあるが、クラスをまとめることができる
  • カプセル化でオブジェクトは責任を分担する

といったところでしょうか。

この記事で紹介した概念だけがオブジェクト指向の全てではありませんが、学習の手掛りとなれば幸いです。

オブジェクト指向を学ぶ中で、プログラミングというものに興味を持ってもらえるととても嬉しいです。提督諸君がプログラミングに目覚めますように!

おすすめ言語

Javaを今すぐ窓から投げ捨てて、オブジェクト指向関数型言語の両方の特長を持つ最高の言語Scalaを使いましょう。

Scalaのここがすごい

  • JVMで動くのでJava並みの速度で動作する
  • Javaの資産をそのまま利用できる(JARを読み込ませることができる)
  • 強力な型システムのサポートにより、nullとおさらばでき、実行時例外を減らすことができる
  • 型推論により、型の宣言を省略することで簡潔な表記が可能に
  • 拡張性の高い簡潔な構文により、DSLを書くことができる
  • IDEが充実している(IntelliJ IDEA)
  • 強力なビルドツール(sbt)
  • パターンマッチ等の言語機能により、デザインパターンを書く労力が削減できる
  • 関数型プログラミングの考え方を導入し、コレクションの操作が簡単にできる

Scalaを始めよう

windymelt.hatenablog.com

Scala逆引きレシピ (PROGRAMMER’S RECiPE)

Scala逆引きレシピ (PROGRAMMER’S RECiPE)

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

プログラミングScala

プログラミングScala

*1:ミリタリー的には、巡洋戦艦という呼び方のほうがメジャーです。

*2:A is a Bと書くとき、BはAのスーパークラスであり、AはBのサブクラスである。

*3:「最低4隻で、軽1隻、駆2隻、他1隻が必要」という表記は、「天龍型もしくは球磨型もしくは...、睦月型もしくは吹雪型もしくは綾波型もしくは...」といった表記をしなければならない

*4:部下の将官の命令に介入するダメな上官も居ましたが

*5:つまり、索敵メソッドを持っている艦娘であれば誰でも良いわけです。特定のクラスではなく中身で識別することをダックタイピングと呼びます。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?