230
190

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向って何だよ!

Last updated at Posted at 2021-12-13

0.ことの始まり

「オブジェクト指向」でググる。読み応えのある検索結果だ。

オブジェクト指向とは、「ある役割を持ったモノ」ごとにクラス(プログラム全体の設計図)を分割し、モノとモノとの関係性を定義していくことでシステムを作り上げようとするシステム構成の考え方。

そして、動物の例が挙げてある。

動物の「鳴く」という機能を一般化してクラスとして実装します。それを「動物」の具象である「犬」や「猫」に対して実装します。これによって、現実世界のモノの見方をプログラムに落とし込むことができます。

本文下線部に 犬や猫をプログラムに落とし込む とあるが、一体どんなアプリを作っているのだろうか・・・?

1.背景

オブジェクト指向は1970年、つまり今から半世紀近く前に提唱された概念だ。今や全てのプログラム言語が「オブジェクト指向の影響を受けている」といっても過言ではない。

オブジェクト指向型言語の代表とされるC++やC#、Pythonのユーザは、すでに少なからずオブジェクト指向的な要素には触れているだろう。今日のシステムはほとんどオブジェクト指向型言語を採用している。

だが、オブジェクト指向はひどく難解で、誤認されがちだ。ひどい記事だと「オブジェクト指向に明確な説明はない」とか「オブジェクト指向は古い」とか書いていたり、ドメイン駆動とプロパティをごちゃ混ぜにしてオブジェクト指向としているケースすらある。

2.記事の目的

そこで、本記事の目的は単純明快

  • オブジェクト指向を理解すること

ただ1つである。

3.この記事を読むべき理由

オブジェクト指向でデザインされたクラスは、極めて再利用性が高く、他のプロジェクトに持って行っても、すぐそのまま使うことができる。保守も非常に簡単である。仕様変更にも強い。アップデートも容易だ。

オブジェクト指向をマスターした後は、まるでコンテナをクレーンで移動させて巨大な建造物を作るようにソフトウェアが設計できるようになる。

プログラミング歴10年目の筆者が保証しよう。オブジェクト指向をマスターすれば、システム設計がそれはもう楽になる。

しかしオブジェクト指向も万能ではない。業務の性質上テストを書くことができない科学者や、とりあえず動くコードが欲しいプログラミング初心者の役には立たないだろう。コードが再利用できない、テストが書けない環境なら、オブジェクト指向はあまり意味がない。むしろ手間が増えるだけで逆効果かもしれない。だから使いどころを選ぶ武器だといえる。

注意点

専門外の人に向けた資料をまとめ直したため、冗長な箇所がある。不明瞭な点や意見・感想はぜひコメントへ。あと、この文章の読み方だが、分かっているところはさっさと読み飛ばして先へ進んでほしい

4.主要な言語パラダイムの流れ

現在を知るためには過去の流れを知ることが必要である。ここで歴史的に知っておくべきものは

  • (1)手続き型プログラミング
  • (2)構造化プログラミング
  • (3)関数プログラミング
  • (4)オブジェクト指向

だ。

(1) 手続き型プログラミング

手続き型プログラミングの定義は意見が割れるところであり、

  • ア 命令型プログラミング
  • イ プロシージャ型プログラミング

のどちらかをそのように呼んでいる。しかし、いずれにせよ 現代のプログラミング言語は、ほぼ全部手続き型 だと思って問題ない。

ア 命令型プログラミング

命令型プログラミングというのは、昔々、ENIACというコンピュータがあった頃の話だ。

なんと恐ろしいことに、このコンピュータは計算の度に回路を繋ぎなおす必要がある。足し算をやって、引き算をやる時にはまた回路を変更するのだ。

これだとあんまりにも不便なので、汎用計算が出来るノイマン型コンピュータというものが設計された。ノイマン型コンピュータでは、MOV, ADD, PUSHなどの汎用命令を駆使してプログラミングする。この 汎用計算が出来る方式命令型プログラミング方式 と呼ぶ。
image.png

現代のコンピュータはすべてノイマン型のため、この定義に沿っていえば現役のプログラム言語は全て手続き型だといえる。

イ プロシージャ型プログラミング

さて、ノイマン型コンピュータの誕生から、少し時代が進んでからの話もしよう。最も原始的なプログラミング言語である「アセンブリ」では、 あるひと塊の処理にラベルを付けて再利用できる仕組み が採用された。

例えば以下のように、一連の処理(=プロシージャ)に対して、「表示処理(PRINT:)」などと命名する。ここでは画面に文字を表示する機能を実装している。

画面に文字を表示する機能(アセンブリ言語)
;表示処理
PRINT:
	mov ax, 0200H
	mov dl, [ds:bx]
	cmp dl, 00H
	jz PRINTEND
	int 21H ;システムコ
	inc bx
	jmp PRINT
PRINTEND:

ここで作った一連の処理は PRINT と5文字書くだけで実行できる。大変便利である。このように あるひと塊の処理にラベルを付けて再利用する仕組みプロシージャ型プログラミング である。この定義によっても、やはり現代のプログラミング言語の ほとんど が手続き型といえる。

※ 「ほとんど」の例外は、機械語である。

(2)構造化プログラミング

手続き型プログラミングの波が過ぎ去ったあと、高級言語というものが誕生する。

高級とは何なのか。食パンが1枚1万円というセレブな話をしているのではない。ここでいう「級」とはレベルのことだ。

以前、「アセンブリは低水準言語ですからねえ」と述べたところ、しばらくアセンブリプログラマを馬鹿にしていた重役が居たが、「真に愚かなのは重役の方である」と筆者は思った。ただし、これはあくまでも架空のエピソードである。この話は大人の事情のため深掘りしない。
 
情報の世界はレイヤ構造になっていて、ある一連の処理に名前を付けて処理をまとめるということを繰り返して出来上がっている。その抽象のレベルを1段上る(=ある一連の処理に名前を付けてそれらをまとめて呼び出せるようにする)というのが「高級になる」ということだ。

初期の高級言語「C・Java・FORTRAN・COBOL…」が開発される頃、「プログラミング言語の本質は、ifとforである」というコンピュータ的特性が知られていた。(この事実は今日でも全く変わっていない!)

そこで、今日のプログラミング言語は全て、 if文とfor文を持つ設計 になった。これこそが 構造化プログラミング の本質である1

(3)関数型プログラミング

前節では構造化プログラミングでif文というものが出来たという話をした。条件分岐について考えてみよう。

読者はゲームを作っている。条件に応じて、与えるダメージ量を変えたいことはないだろうか。下の例を考えよう。 TAKE_DAMAGE プロシージャにはダメージ計算の式が書かれている。

ダメージを与えるプログラム(プロシージャ方式)
int hp;
int damage;
int defence;

TAKE_DAMAGE:
    if ( damage - defence > 5 ) hp -= damage * 2;
    if ( damage - defence < 5 ) hp -= damage;
    ・・・

この TAKE_DAMAGE ラベルの中身が更に500行膨らんだときを考えてみよう。このとき戦慄のプログラムが誕生する。なぜ恐ろしいのか。それは、ダメージ量がHP, damage, defence以外の変数によっても決定される可能性があるためだ。

こんなプログラムは最悪である。その難解さのせいで、プログラマーにスパゲッティコードを量産する、空飛ぶスパゲッティモンスター という不名誉なあだ名が付くかもしれない!

ここから想像できる事故を未然に防ぐため、 決まった入力から明確に出力が決まる「関数」という表記方法 が使われるようになった(下はその例)。

ダメージを与えるプログラム(関数)
int hp;
int attack;
int defence;

int Damage (int attack, int defence) {
    if ( damage - defence > 5 ) return damage * 2;
    else if ( damage - defence < 5 ) return damage;
    ・・・
}

この方法では、ダメージが必ず attackdefence というパラメータによって決定される事が確定しているため、まともに設計されたソフトウェアなら事故は起きない。

また、関数には単に入力と出力が決まる以外のメリットもある事が分かってきた。例えば、自分自身を指定することで漸化式を記述できるという強力な利点がある。次に例を挙げる。

漸化式(例:5+4+3+2+1=13 を求める)
// 5+4+3+2+1
// Result: 13
print( "Result: " + func(5) );

int func(int n){
    if (n==1) return 1;
    return n + func(n-1);
}

ここでは func(n)func(n-1) を呼び出す。すなわち、関数が自分自身を参照して漸化式を記述している のである。このやり方を 再帰 と呼び、競技プログラミングで必須の手法である2。初項 f(0) を設定しないと無限ループに入ることも有名である。

このように、 入力から出力が一意に決まる「関数」を用いたプログラミング関数型プログラミング と呼ぶ。

(4)オブジェクト指向

そしてオブジェクト指向の説明に入るわけだが、オブジェクト指向は、ここまで見てきたパラダイムとは全く違う。既に起きている問題を解決するための物ではないのだ。ベースの着想は「こんな考え方出来たら面白い」などと一見してあほらしい3考え方だ。

よって、先に言っておくと、残念なことにオブジェクト指向をマスターしても、難しい問題を解く上での困難は何も解決しない。関数型プログラミングのように「再帰ができるから高度なアルゴリズムが簡単に記述出来る」なんてこともない。ただし 変更に耐性を持ち、再利用も容易なスーパーコードを書けるようにはなる。システム屋には非常に喜ばしいが、数学者には1ミリも役立たない4

それでは、オブジェクト指向の説明に入ろう。

5.オブジェクト指向とは何か

オブジェクト指向が何かを理解するためには、最初に「オブジェクト」を理解するのが近道だ。

信頼性の高い複数の情報源(Qiitaや技術ブログなど)で、オブジェクトは「現実のモノだ」とか「コンピュータで処理できるデータの塊だ」という説明を見かける。これは嘘である

(1)オブジェクトとはオブジェクトである

オブジェクト指向における「オブジェクト」の指すところだが、これは日本語で言う モノ だ。情報の世界に存在していて万人が認識できる対象ということが必要だ。他に一切の制約はない。

情報の世界に存在して万人が認識できる 」とはどういうことか?

ズバリ、説明しよう! 下はゲーム画面のスクリーンショットである5
20111206unityTraining-thumbnail2.jpg

GUIのボタンアイコン3Dのキャラクターが目視で確認できる。そして目では見えないが、データを保存するストレージや、サーバへ行くためにネットワークを流れるデータといったモノが存在する。

これらの例は全てオブジェクトである。確かに情報の世界に存在して万人が認識できる。だから、オブジェクトと言える。

ア 「オブジェクト」という概念の新規性

「当たり前の事を言っただけではないか!」という読者の心の声が聞こえる。オブジェクト指向の新規性を説明するため、古典的プログラミング(ファミコンを思い描く)との違いを説明したい。

Undertaleというゲームを例にする。白い四角の中で自キャラを操作して弾を避けるシューティングゲームである。
undertale1.jpg
終盤のボス戦では、この白い四角の枠をプレイヤーを使って動かせる6

undertale-40-10.jpg
この1シーンは正に、従来のファミコンゲームの設計思想と異なる考え方を底流に持っているといえる!

オブジェクト指向以前のゲームにおいて、当然、GUIでキャラクターを動かすことは出来た。しかし逆に、キャラクターがGUIに触ったり、GUIに対して変更を与えたりすることは許されなかった。この世界線でのGUIとは「タダの線」で、ただ単に画面上で描画される図形に過ぎないからだ。

だがオブジェクト指向以後、もはやGUIは画面上にぶちまけられた「タダの線」ではない! 上の図で白い四角の枠は「枠という物体」なのだ。だからキャラクターが触れても良いのである!

イ オブジェクトは、日本語で言うところの「モノ」である

日本人的な感覚によれば、オブジェクトとは八百万の(神様が宿っている)モノという捉え方が近いだろう。データもオブジェクトに含まれるというのは確かに直感に反する。しかし、八百万の神様が宿る物体だと思えば納得できる気がする7

オブジェクトには「八百万の神」が宿るというからには必ず万人が認識できる呼称をする必要がある。山の神様はいるが、右半身+左足の薬指みたいな神様はいない。同様に、TopPage というクラスは認められるが、 DBを管理GUIも操作 みたいなクラスは自然の摂理に反するので通常は認められない。

 
最後に本節の要旨を述べる。オブジェクト指向は上で説明した「モノ」でプログラムを組立てる考え方 である。

6.オブジェクト指向の実装

「オブジェクト」指向の理屈は分かった。もっと完全に理解させてくれよ!という読者の心の声が聞こえる。具体的実装の要諦へ進もう。

オブジェクト指向が劇的に上達する秘伝の掟を伝授する。それは

(1) -erで終わるクラスを作るな
(2) ググって出てくる名前を付けろ

の2個だ。

(1)-erで終わるクラスを作るな

まず前者を解説しよう。最初に聞いた時は、筆者も「何言ってんだこいつ」と思った。

プログラマとして中級レベルに差し掛かってからというものの、筆者はController, Manager, Helper, Drawer...etc を量産した。それからこれらの区別がオブジェクト指向マスターだと、そう信じていた。

しかしこの考え方は全くの間違いであった。「-erで終わるクラスを作るな」という教えの真の意図を理解したとき、目から鱗が落ちた のである。

 
なぜ-erで終わるクラスを作ってはいかないのだろう? ここで、-er で終わるクラス名の代表格を挙げてみよう: Controller, Manager, Helper, Renderer...

ところで Controllerとは実際のところ、何なのだろう? 目を背けたくなるような真実を説明するため、実際に問題となったGUI Controller クラス(コンポーネント)を考えよう。

2.PNG

1.PNG
(これは Script という名の「神オブジェクト」にアタッチされたプログラムである)

このようなコードでは、最初のバージョンアップから、「どのコードに何を書いたらいいか分からない」問題8が発生する。おそらく最初に作られた時点ではGUIの色々を制御する便利クラスだったのだろうが、スクロール処理をやり、データの更新処理をやり、ボタンの処理を書いて・・・とする内に中央集権的な「神クラス」と化してしまう。

どうしてこのような事象に陥るのだろうか? そもそもGUI Controllerとは何だろう?

GUI Controllerと聞いて、思い付く具体的な関数の中身を書き出すことは出来るだろうか?否、具体的構造が思い浮かぶ人は皆無だろう。

 
では、User Data はどうだろう? 読者の頭の中では大体こんな感じのデータ構造が浮かんだのではないだろうか。

データ構造
・ユID (※ユザ毎に一意な英数字)
・ユザ名
・アイコン画像
・メルアドレス
・プロフィルの一言
     ...

なんと分かりやすいデータ構造だろう!

GUI ControllerUser Data は何が違うのか。単純に考えれば末尾に -er が付くか、付かないかである。いかなる理由であっても -er で終わるクラスを作ってはいけない!(※)

例外

「いかなる理由でも~いけない」などと偉そうに言いながら、以下の例外がある。理由は次節で解説する。

  • Manager オブジェクトが「マネージャー」という具体的役職を表す場合
  • Controller オブジェクトが「ゲームのコントローラ」という具体的物体を指す場合

(2)ググって出てくる名前を付けろ

User Data の中身は誰でも想像し得た一方、GUI Controller の中身を想像することは極めて困難であった。後者の場合にはプログラムの仕様変更が非常に難しい。仕様変更を容易にする為、クラスには誰でも中身が分かる(ググって出てきそうな)名前を付けるべきだ。

ア GUI Controllerとの別れ

MVCモデル風の GUI Controller はどう命名すれば良かったのだろうか。一緒に考えよう。

(ア)管理対象物に注目(GUI

まず、このオブジェクトが管理する対象物は GUI である。だからそもそも GUIController という名前はやめて、GUI と命名しよう。

(イ)適切な抽象度へ分解(Home MyPosts Menu

しかし GUI という名前はザックリ度が高い。もっと分割して Home MyPosts Menu... だったらどうだろう? 例えば Home はホーム画面に存在するオブジェクトを処理し、ホーム画面を一番上に戻す処理とか、初期化処理をする仕事を担う。

ただし、 Home には間違っても「ホーム画面上にあるボタンが押された時の処理」を書いてはいけない。ボタンの処理はボタンにやらせるのである。

オブジェクト指向帝国で越権行為は厳しく禁止されるので、HomeMenu 画面へ過干渉するような実装は技術的に可能であっても避けるべきである。

(ウ)より妥当な名前へ(Page

実はそもそも Home MyPosts Menu... に分ける必要すらない。これらのコードの中身は同じような感じになるので、Page というクラスにまとめてしまう事もできる。不都合があっても元に戻すか、Page クラスから派生した Home MyPosts Menu クラスなどを作ればよいだけだ。

こうすれば、忌々しい GUI Controller クラスと別れを告げ、Page を実装することが出来る。


少し難しい解説をすると、GUI Controller の場合は

  • HomeViewを一番上に戻す
  • メニュー画面へ遷移する

などの「手続き」や操作に意識が行く。一方で、Page の場合はページという「モノ」に焦点が合う。オブジェクト指向では、オブジェクトの名前を正しく設定する事が最も重要だ

先の「例外」の説明

前節で、管理職であるマネージャ(Manager)や ゲームコントローラ(Game Controller)は例外であると述べた。これらは確かに -er で終わるクラスである。しかしこれらは実在する物であり、ググれば具体物がすぐに判る。

それに「管理職(Manager)」の守護神や「ジョイスティック(Game Controller)」の神は、「GUI Controller」の神に比べて高い確率で居る気がする。

-erがダメだという教えの本質は、アプリごとに土着したスパゲッティモンスター(神クラス)を誕生させない点にある。-erが付くものを何も考えないで教条主義的に禁止する事は又、本末転倒である。

 

イ アプリ内のデータについての考え方

アプリ内のデータに対しても気を配るとベターだ。SNSの例を考えよう。

SNSでは、大体こういう

  • ① 自分のアイコンと名前
  • ② 投稿日
  • ③ ひとこと
  • ④ 反応

の付いた物体に遭遇する。カードみたいに見えるので便宜上「カード」と呼ぶ。

この「カード」を実装する時、 CardData という名前で、データをコンポーネントの形で持っておく実装をオススメしたい。例えば、

CardData の データ構造
・名前
ID
・投稿日
・投稿内容(テキスト)
・画像 ・・・

といった感じである。投稿した時間など、例え今直ぐに表示しない情報であっても、データ構造の上では念のため全てをCardDataへ保持しておくべきだ。

そうすれば仮にメテオフォール開発において、顧客なる神が「やっぱり投稿日時は要らないよなあ」などと終盤に今更訳わからんことを言い出したとしても

このようにカードの表示部分を変えるだけで済み、わざわざクラス設計までやり直す必要はない。この章の戒律を守れば、読者の徹夜は間違いなく減るだろう。

※ この程度のメテオで「グエー死んだンゴ」などと述べる者はヒヨッコである。そもそも炎上案件を鎮火するのは次善策である。本物の「消防士」はこうした事態を見越して最初から仕様変更できる設計にし、炎上を未然に防ぐため、派手な表の舞台には出てこない。

ウ ○○Helper、○○Playerの場合

○○Helper、○○Playerについても名称を改善可能だ。

例えば、

  • DataGetHelper なら UserInfo / UserDatabase
  • StreamFilePlayer なら Movie

へ直す、といった具合である。

エ 例外

上級者向けのTipsだがバックエンドやフロントエンドのAPI呼出箇所など、ネットワークやDB・バックエンドが絡む場合の設計は本章の限りでない。データの扱いにあたり、オブジェクト指向に加えてDB設計の技法(DOA、データモデル)を理解し、両世界の矛盾する設計を国語力で以て統合することが必要である。

7.オブジェクト指向の3大要素

ここまで解説してきた「オブジェクト」の正体を理解すれば、自ずとオブジェクト指向の真の姿が見えてきただろう。その上で、次はオブジェクト指向の3大要素だ。

この3要素という名前に惑わされるやつが多い。要素は枝葉末節に過ぎない。「オブジェクト」を理解せずにこの議論を始めることは愚かだ。「オブジェクト指向とは何か?」という問いで、最初に3要素の話から答え始める記事を見た。彼らはオブジェクト指向エアプレイヤーだ!

だが「オブジェクト」の意味を完全に理解した者は、果敢に次の段階へ進むべきだ。

以下がその3要素である。

(1)継承
(2)カプセル化
(3)ポリモーフィズム

カプセル化」は唯一オブジェクト指向の本質に近い。他の二つは付属品だ。

(1)継承

まずはインデックス通りに最初の要素「継承」について語る。 この機能はプログラムの単なるコピぺ機能ではない! 筆者もオブジェクト指向を語り出したころ「要するにコピペ機能でしょ」と豪語していたが、この認識は大きな誤りである。

ア 「継承はコードのコピペ機能」という考えは誤り

「継承をあえて一言で説明すると、コピペ機能だ」という主張は、機械(コンパイラ)から見ると完全に正しい。

例えばここに3つのスクリプトがある:

A. 円の面積を求める Menseki.cs

Menseki.cs
public class Menseki(){
    public float pi = 3.14f;

    // 半径x半径x3.14 した値を返す
    public float Area_Circle(int r) {
        return (r * r * pi);
    }
}

B. 円柱の体積を求める Volume_Cylinder.cs

Volume_Cylinder.cs
class Volume_Cylinder : Menseki {
    // 底面積x高さ の値を表示する
    void Volume_Cylinder(int r, int height){
        print( Area_Circle(r) * height );
    }
}

C. 円錐の体積を求める Volume_Cone.cs

Volume_Cone.cs
class Volume_Cone : Menseki {
    // 底面積x高さx(1/3) の値を表示する
    void Volume_Cone(int r, int height){
        print( Area_Circle(r) * height * (1.0f/3.0f) );
    }
}

これらのコードは事実上、次のコードと等しい。

A. 円の面積を求めてから円柱の体積を求めるCylinder.cs

Cylinder.cs
float pi = 3.14f;

// 半径x半径x3.14 した値を返す
float Area_Circle(int r) {
    return (r * r * pi);
}

// 底面積x高さ の値を表示する
void Volume_Cylinder(int r, int height){
    print( Area_Circle(r) * height );
}

B. 円の面積を求めてから円錐の体積を求めるCone.cs

Cone.cs
float pi = 3.14f;

// 半径x半径x3.14 した値を返す
float Area_Circle(int r) {
    return (r * r * pi);
}

// 底面積x高さx(1/3) の値を表示する
void Volume_Cone(int r, int height){
    print( Area_Circle(r) * height * (1.0f/3.0f) );
}

実行結果だけ見ると両者から得られる結果は同じだ。継承という機能によって、基底クラスの中身が派生クラスの一番上にそのままコピペされているように見える。コンピュータ内の動作の理解として「コピペ」という認識は全く誤っていない。

しかし、継承には実使用にあたって人為的にルールが設定されており、このような認識はいけない!

イ 継承とは「派生」である

歴史的に、そもそも「継承」という呼び方は変化形である。JavaやC#では「継承」と呼ばれてきたこの機能だが、元祖オブジェクト指向言語「Smalltalk」のことを思い出してほしい。皆忘れてしまったのだが、この機能は元々、inherit (継承) ではなく、derive (派生) と呼ばれていた

derive (派生) とはこういう事である。例えば「ヒト(属)」と「ゴリラ」は「哺乳類(サル目ヒト科)」に属する。「ゴリラ」は大括りで「哺乳類」から派生し、我々(ヒト)の仲間である。

そして小難しい解釈をすると、サブクラス(ゴリラ)は、スーパークラス(哺乳類)から派生 している。サブクラス(ゴリラ)はスーパークラス(哺乳類)の性質(おっぱいを飲む)を持つ。実はゴリラもおっぱいを飲むのだそう。これ豆知識。

ゴリラはおっぱいを飲むのでスーパークラス(哺乳類)から派生した種族である。しかし逆に、全ての哺乳類が必ずしも素手で丸太を折ることはできない。だからスーパークラス(哺乳類)はサブクラス(ゴリラ)の特性を完全に引き継いでは行けない!
 
同様に、先の面積の議論へ戻ると、体積面積から派生させてはならない。ヒトゴリラ ではないだろう。同じく、体積面積ではない!(体積 面積 derives from ユークリッド空間図形. ←これは問題ない!)。

補足:is-a ⇔ has-a

この継承の議論(ゴリラの話)でよく使われるのが、is-a ⇔ has-a である。ゴリラ is 哺乳類ゴリラ has おっぱい だ。ゴリラと哺乳類はis-a関係となり、継承の対象(ゴリラ derives from 哺乳類)となる。has-aの場合はコンポジションの対象となる。

ウ 正しい継承の使い方

(ア)正しいコード

先の「面積」クラスを継承(派生)を使って正しく書き直してみよう9

円や三角といった平面図形は「形(面積を持つもの)」の派生で、球や直方体などの空間図形は「立体(体積を持つもの)」の派生と考える。そうすると、コードは以下の様になる:

using System;

interface Shape {
    double Area();
}

interface Solid : Shape {
    double Volume();
}

class Circle : Shape {
    public double R { get; private set; }
    public Circle(double r) {
        R = r;
    }
    public virtual double Area() {
        return R * R * Math.PI;
    }
}

class Triangle : Shape {
    public int Bottom { get; private set; }
    public int Height { get; private set; }
    public Triangle(int bottom, int height) {
        Bottom = bottom;
        Height = height;
    }
    public double Area() {
        return Bottom * Height / 2;
    }
}

class Square : Shape {
    public int Size { get; private set; }
    public Square(int size) {
        Size = size;
    }
    public double Area() {
        return Size * Size;
    }
}

class Rectangle : Shape {
    public int Width { get; private set; }
    public int Height { get; private set; }
    public Rectangle(int width, int height) {
        Width = width;
        Height = height;
    }
    public double Area() {
        return Width * Height;
    }
}

class Ball : Circle, Solid {
    public Ball(double r) : base(r) {
    }
    public override double Area() {
        return base.Area() * 4;
    }
    public double Volume() {
        return Area() * R / 3;
    }
}

class Corn : Circle, Solid {
    public double Height { get; private set; }
    public Corn(double r, double height) : base(r) {
        Height = height;
    }
    public override double Area() {
        return base.Area() + Math.PI * R * Math.Sqrt(R * R + Height * Height);
    }
    public double Volume() {
        return base.Area() * Height / 3;
    }
}

class Cylinder : Circle, Solid {
    public double Height { get; private set; }
    public Cylinder(double r, double height) : base(r) {
        Height = height;
    }
    public override double Area() {
        return 2 * base.Area() + Height * 2 * R * Math.PI;
    }
    public double Volume() {
        return Height * base.Area();
    }
}

public class Program {
    public static void Main() {
        Shape[] shapes = {
             new Circle(10), new Circle(20), new Circle(30),
             new Triangle(10, 10), new Triangle(20, 20),
             new Square(10),
             new Rectangle(10, 20),
             new Ball(10),
             new Corn(10, 10),
             new Cylinder(10, 10),
        };
        foreach (var shape in shapes) {
            Console.WriteLine(shape.Area());
        }
        Solid[] solids = {
             new Ball(20),
             new Corn(20, 20),
             new Cylinder(20, 20), new Cylinder(30, 30),
        };
        foreach (var slid in solids) {
            Console.WriteLine("{0}, {1}", slid.Area(), slid.Volume());
        }
    }
}

実行結果は下のようになる。

314.159265358979
1256.63706143592
2827.43338823081
50
200
100
200
1256.63706143592
758.447559174816
1256.63706143592
5026.54824574367, 33510.3216382911
3033.79023669926, 8377.58040957278
5026.54824574367, 25132.7412287183
11309.7335529233, 84823.0016469244

円は平面図形であり、球は立体図形だ。それぞれ必ず面積、体積を有しているという性質を持つ。そして実行結果も正しい。

(イ)どうして継承を使うに至ったのか?

上のコードで、ShapeSolidといったクラス10は、最初のコードより前に、天から突然降ってきたのではない。

先に円形や三角形があった → 次に平面図形というカテゴリを設けた のである。
哺乳類というカテゴリを設けた → チンパンジーが誕生した のではない。サルやチンパンジーを見た → 哺乳類というカテゴリを設けた のである。

継承は親から子への派生だが、子を沢山眺めているうちに、親カテゴリを思い付くというのが自然な発想だ。だから、継承の設計において、子が1つも出来ていないのに、いきなり親の話が出てくるのはおかしい。継承の出番は設計または実装を進める途中で「AとBとCが仲間だ」と気付いたときなのである。

エ 継承は忘れよう

ここまで解説しておいてなんだが、継承のことはしばらく忘れよう。というのも、この機能を使うべきタイミングは非常に限られており、人間の愚かさと宇宙は無限だからだ。継承に関する以下の2問題について指摘せねばならない。

(ア)怠惰なオーバーライドは教義違反

継承に関連してオーバーライドという機能がある。継承したスーパークラスの性質を書き換えるズル技のことである。これに関連した問題がある。

現代のプログラミング言語はあまりにも複雑である。例えば VB.Net の Button クラスひとつを見ても、用法によっては到底使わないようなメソッドまでも存在しており、この Button クラスを継承して新たなクラスを作るのは事実上不可能だ!

確かに、Button クラスの亜種(3D Button Rainbow Button Animation Button …)があったら便利だと思うことはある。では、何も考えずにButton クラスを継承・オーバーライドし、亜種を量産して良いのだろうか?

否である! これらの Button(亜種) はMicrosoft社の考えるButtonの定義を書き換えているため、VB.Netの世界でButtonと名乗ることは許されない違法物品となる。このような場合にオーバーライドは教義(サブクラスはスーパークラスの枠組へ従う)違反にあたる。

言葉遊びじゃないか!と言われるかもしれないが、一方で、VB.Net自分の世界観 を統合するためにやむを得ず最小限の範囲で、Button を再定義する場合はオーバーライドを用いた継承は善なる使い方となる。単にサボりたいから継承を使うのは悪い意味で怠惰であり、VB.Netの世界自分の世界 の矛盾を解消するために最小限の範囲でオーバーライドを用いるのは正しい。

しかしこの区別は初心者には中々難しい。

(イ)運用努力問題

継承の使い方はプログラマの心構え・裁量によるところが大きいため、継承の使い方への理解が浅いと却って保守しづらいソースコードが出来る。

そもそも、以下の2つは似たようで全く違う。

① コードやオブジェクトの 重複を減らして単に1つにまとめる
② オブジェクト同士に共通する性質を 1つにまとめて適切に名前を付ける

① は先に述べた最悪の使い方である!これは端的に言えば「エアコン&テレビ&ドア」を合体した物体を作り出す行為である。そんなコードの保守が面倒なのは明らかだ。継承を使えば(使わなくても)「プログラミングが楽しい」などという事はあり得ない!「勉強が楽しい」と言っている中学生のようなものだ。そんなことはあり得ない。

② は素晴らしい。例えて言うなら「エアコン」「テレビ」「ドア」に共通する「ネジ」を作り出す行為である。大変合理的だ。真に怠惰なプログラマは後で楽をするために手間を惜しまないものだ

と、このように継承は使い方次第で天と地ほどの違いが出てしまうので、やはり継承の事はしばらく忘れておくべきである。

オ いつ継承を使うのか?

継承の全てを理解した。そして、これを使ってみたいのは分かった。しかし、実際に使う前に

  • (ア)フィールドやインスタンス化・コンポジションで解決できないか
  • (イ)重複はそのままにしてはダメか?

をちょっと考えてみてほしい。それでも上位の抽象概念が必要ならここで継承を使うのが良いだろう。

(2)カプセル化

次に第二要素「カプセル化」だ。カプセル化は曲者で、データ保護機能と勘違いしている人が多い。たしかに大学の講義や技術書でもこう説明されているので、筆者も長年そういうものだと思い込んでいた。

しかし、この考え方だとどんなコードを読んでも意図が理解できないだろう。この考えは誤りだったのかもしれない。今日から新しい認識をしてみよう。

ア カプセル化とは「カプセルにすること」である

表題にもあるように、カプセル化の意味とは「カプセルにすること」だ。この業界において「カプセル」という言葉は絶対的な意味を有する。図で表すと、下の図のようになる11

Capsulation.png

複雑な処理は全部カプセルとして閉じ込め、あるボタンを押すだけで全部の処理をやってくれるようにする。中の実装は知らなくて良い。これがカプセル化だ。

イ カプセルに正しい名前を付ける

オブジェクト指向の本質は、「オブジェクト」へ明確な名前を付けることであった。ではカプセル化にはどのように名前を付ければ良いのか。具体例を考えよう。

まずは悪い例である。次のコードはトランプを表している。

Card.cs:カードを番号で管理
public class Card {
    public Card(int code) {
        num = code;
    }
    public int num = 0;
}
Test.cs:カードの番号を図柄へ変換
class Test {
    void Main() {

        Card card = new Card(0);

        string suit = card.num / 13 switch
        {
            0 => "スペード",
            1 => "ハート",
            2 => "クラブ",
            3 => "ダイヤ",
            _ => throw new InvalidOperationException()
        };

        print(suit + "の" + (code % 13 + 1)); // スペードの1

    }
}

これは、トランプのコードである。ロジック自体はよくあるやり方で、全部のカードに 0 ~ 51 の通し番号を振るのである。

  • 0 ~ 12までは「スペード」のカード
  • 13~25までが「ハート」のカード
  • 26~38は「クラブ」のカード
  • 39~51が「ダイヤ」のカード

である。例えば 14 番は "ハートの2" に対応する。

さて、ここで Test クラスに注目すれば、上のコードはオブジェクト指向として適格でなく、違法建築ということが明らかである。オブジェクト指向帝国でこれが違法たる最大の要因は命名が適切でないからだ。工場での試作時はOKだが、人の家ではダメだ。

Card クラスにも注目し、よく考えてほしい。普通、トランプはこうだ。

こうではない。

先ほど見た Card クラスは後者の実装をしている。public Card(int code) の部分を見れば明らかだ。どこにそんなトランプがあるのか! そんなトランプは現実には存在しない! よって違法であり、こんなコードを見かけた日には控えめにキレてもいい。

では、次のような実装はどうだろうか12

PlayingCard.cs: トランプは必ず絵柄と数字を持つ
// トランプ
interface PlayingCard
{
    int Suit();    // 絵柄
    int Number();    //数字
}
Card.cs: 通し番号と絵柄・数字を持つトランプ
// int型のコードで管理されるトランプ
class IntCard : PlayingCard
{
    readonly int code;
    
    public IntCard(int code)
    {
        this.code = code;
    }

    public int Suit()
    {
        return code / 13;
    }  

    public int Number()
    {
        return code % 13 + 1;
    }

    public override string ToString()
    {
        string[] suit = {"スペード", "ハート", "クラブ", "ダイヤ"};
        return suit[this.Suit()] + "の" + this.Number();
    }
}
使い方
PlayingCard card = new IntCard(0);
print(card);  // スペードの1

Card という54枚の物体は、PlayingCard の一種であり、全て「絵柄」「数字」という共通の性質を持つ。さらに、それぞれ個別の通し番号が付いているが、それは内緒の性質であり公表されていない。

これがオブジェクト指向的な "トランプ" であり、先の図でいえば、前者:

に相当する。こちらの方が何となく正しいコードのような気がする。

このようにモノへ正しく名前を付け、疑似的にモノを表現する仕組みがオブジェクト指向(カプセル化)の本質である。モノの使いやすさ、便利さを考えるデザイン的要素がオブジェクト指向の難しさでもあるのだが、ここは別に良い記事があるのでそちらを読んでほしい11

ウ 補足:「求めるな、命じよ」

このような実装をするためにTell, Don't Ask. (求めるな、命じよ)の原則13が広く知られている。

下のダメな例では、敵のHPを「求めて」ダメージを与えているが、良い例では敵に「命じて」ダメージを与えているという違いがある。他人のHPを勝手にいじるのは越権行為である。

ダメな例
// 敵のHPを「求めて」ダメージを与える
void Attack(){
    enemy.hp = enemy.hp - attackPower;
}
良い例
// 敵に「命じて」ダメージを与える
void Attack(){
    enemy.takeDamage(attackPower);
}

「ここから先(あなたのHP)はそちらの都合」ということにして、残りは相手のオブジェクトで処理してもらう考えが、プログラマの世界では一般的な実装だ14

(3)ポリモーフィズム

さて、最後の要素であるポリモーフィズムについて解説しておこう。ポリモーフィズム自体は多態性を意味する言葉だ。

ポリモーフィズムという言葉も多態的である。その意味すら一つでなく、状況によって異なる意味を指す。例えば、以下の3つ15を指すケースがある。

  • ア オーバーライド(≒サブタイピング)
  • イ コンポーネント化(=パラメータ多相)
  • ウ オーバーロード(=アドホック多相)

オブジェクト指向の文脈においては、オーバーライドを指すのが一般的な感覚だが、読者がググる手間を減らすため、順にすべて解説する。

ア オーバーライド

こいつは 継承の親戚 だ。継承に慣れてくると、広く一般のクラスに対して処理をしたい瞬間が訪れる。

例えば、下のように Duck(アヒル), Cuckoo(カッコウ), Bird(鳥) クラスを定義したとき、

アヒルとカッコウ
public class Duck : Bird {}
public class Cuckoo : Bird {}

public class Bird {}

次に挙げる3つのやり方はどちらも正しく動く。

3つのやり方
public class Main() {
    public void Start() {
        Bird bird1 = new Bird();
        Bird bird2 = new Duck();
        Bird bird3 = new Cuckoo();
    }
}

上でやっている事はシンプルだ。

  • Bird <- Bird の代入
  • Bird <- DuckBird <- Cuckoo の代入

でもちょっと待って!「Duck ≠ Birdじゃないか!」と思うかもしれない。本当は Duck ⊂ Birdだが、便宜的にこの書き方で、Duckの基底型であるBirdが参照され、自動でBird bird2へインスタンスへの参照値が代入される仕様になっているのだ。

小難しく言うと、派生型(Duck, Cuckoo)は、基底型(Bird)のインスタンスになることができる。これがサブタイピングである。

前章で少し述べたオーバーライドというのは、このサブタイピングに関連する機能である。継承元のコードを書き換えることが出来るのだが、本記事では詳細を解説しない。

イ コンポーネント化

コンポーネント化についても触れておこう。これは オブジェクト間でコードを使いまわす 仕組みである。

3人エージェントがいて、それぞれPaul, Smith, Johnと呼ばれていることにする。

「これらは違うオブジェクトだから3個スクリプト作らなきゃ。Paul クラス、Smith クラス、John クラス…?」

少し待ってほしい。Agent クラスを作って、コピーした方が労力が掛からなくて良いのではないだろうか。名前と走る速さ・パワーをフィールドとして持てば良いのではないか?

こうして共通の性質を持つもの(Paul, Smith, John)に対して、共通のクラス(Agent)を割り当てるのがコンポーネント化だ。 個別のオブジェクトすべてに対してクラスを割り振るのは、あまりにも大変だ。だからこうしてモノの性質の一般化を図っているのである。

ウ オーバーロード

最後はオーバーロードだ。オーバーロードは 同一コード内で関数名を使いまわす 仕組みだ。

ある関数に対して string 引数を使いたい時もあれば、int を引数に取りたい時もある。例えば、次のコードを見てほしい。

Speak.cs: 同じような関数が3つ並ぶ
class Speak(){
    void Say(string text){}
    void SaySpeed(string text, int speed){}
    void SayBreak(string text, int speed, int breakTime){}
}

引数に応じて、関数名も3通り作ったのである。かつてはこんなコードが平気で書かれていた。しかし、最近のC#では引数を変えても関数名は1つで良い。

Speak.cs: 同じ名前の関数が3つ並ぶ
class Speak(){
    void Say(string text){}
    void Say(string text, int speed){}
    void Say(string text, int speed, int breakTime){}
}

これらア~ウの例がポリモーフィズム(多態性)として一般に認知されている。しかし、カプセル化や継承に比べると、ずいぶん緩い感じであろう。

8.オブジェクト指向の利点と課題

ここまでオブジェクト指向の理解のために紙面を割いてきた。もしも紙なら一冊の本になっているだろう(電子媒体の時代に生まれて良かった)。最後に、オブジェクトには具体的にどのような利点があるのか、どのような課題があるのか考えてみよう。

(1)利点

オブジェクト指向設計に3つの利点がある。

  • ア コードを使いまわせる
  • イ 変更に強いコードを書ける
  • ウ プロジェクトの全容を把握しやすい

ことだ。

ア コードを使い回せる

三者のうち最も重要なのは、コードを使いまわせる点である。オブジェクト指向で書かれたプログラム資産の蓄積によって、一晩でアプリが完成する可能性 が示唆される。

コードを使いまわすための代表的な仕組みはモジュール化であろう。

(ア)モジュール構想

従来のモジュール構想(C#.NETではNuGetなど)の問題は、モジュールの肥大化であった。各クラスやモジュールのサイズが大きくなり、いわゆる "神クラス" とまでは行かないまでも数十万行単位の非常に長大なコードやdllファイルが組み込まれる。要するに、モジュール内の小さな変更と応用がすぐに出来なかった。

しかし、オブジェクト指向の考え方では、この問題は偶然にも解決されるかもしれない。カプセル化されたオブジェクトクラスは、せいぜい数千行と1単位毎のコードサイズが小さい。ひとつのパッケージはいくつものスクリプトの集合体であり、用途に応じてスクリプト単位で差し替えが可能となる。この方式は、巨大なdllを一つだけ組み込む方式よりも高速かつ細やかにアプリを組立てることができる。

個別コードの使いまわしが出来る点もメリットである。例えば User というオブジェクトがIDとパスワードというフィールドを持っているとしよう。同じ構造で良ければ、User クラスは別プロジェクトへ持って行ってそのまま利用出来る。これは特に同じようなシステムやゲームを作る場合に有利な仕組みである。

ただし、このコードの使い回しが通用するのは扱う領域が同一ドメインで、言葉の意味が同じ場合である。同じ Card でも、SNS領域とゲーム分野では全く意味が違うので、この場合同じオブジェクトは使い回せない。

補足:ドメイン

ドメインについて補足する。ここに1本のペンがあると仮定する16

image.png
ユーザにとってこれは「字を書く道具」だが、文房具屋さんにとってペンは「売るとお金が手に入る商品」だ。

この「ユーザにとって」とか「文房具屋さんにとって」という部分を ドメイン と呼んでいる。「字を書く道具」とか「売るとお金が手に入る商品」というレベルで、ドメインにおいて捨象されたペンが ドメインモデル である。

(イ)UnityPackageについて

先ほどモジュール構想の話をした。C# (.NET) でも NuGet などとして実装されているが、Unityでは進んだモジュールの在り方として、UnityPackageというフォーマットが整備されている。

UnityPackageでは、複数のC#のコードをパッケージにして別のプロジェクトへ移送することが出来る。一度パッケージを作れば、何回でも同じ組み合わせでコードを使いまわせるのだ。もちろん、中身はC#コードであるから、各ファイルを書き換えたり、差し替えたりすることも簡単である。

(ウ)再建設

何回でも使いまわす前提のコードを作る事の代償として、設計の時間が延びる。この辺の事情は建設業界と似ている。

モジュールを束ねて、巨大なアプリという建設物を作るためには、各パーツが接合しやすい形になっていなければならない。粗悪なモジュールは良くないので、予めよく考えてプログラムを作っておくべきである。

他方、ソフトウェアの良さは、建設と違って割と容易に土台から作り直せるという点にもある。1回作ってみて、ダメだったら直せば良いのであって、その分資材にお金がかかるということはない(人件費はかかるが)。

3回作り直すことを想定してかは知らないが、工期見積を最短納期の3倍で見積もる「3倍見積法」という手法が現場では流行っているらしい。

イ 変更に強いコードを書ける

オブジェクト指向のもう一つの利点は、変更に強いコードを書けることだ。システム開発だと実装を進める中で、仕様が変わることがある。「DBのキーを1つ増やしたい」「GUIのレイアウト変えたい」← こういうことは日常茶飯事だ。

このとき、GUIControllerLoginControllerを使っていると何が起きるかというと、膨大なこれらのクラスの中を探しまわってコードを修正していくことになる。コミュニケーションコストもかかるので良くない。

オブジェクト指向であれば人間の直感に近い考え方を取っているので、修正すべき場所がすぐに分かる。修正箇所もかなり明確である。非オブジェクト指向の実装と比べて、オブジェクト指向の実装ではコード量も増加するが、それ以上に保守の面で良い事があるだろう。

ウ プロジェクトの全容を把握しやすい

オブジェクト指向の3つ目の利点は、全容の把握を容易にできることだ。

オブジェクトの考え方は、人間の考え方にかなり近い。直感的にオブジェクトの役割と動作を把握できる。また、プログラムの説明も容易だ。

大規模開発だと人が替わってコード内容を説明することがある。

その時、「こういうモノとこういうモノがあって」伝えるだけで良く、ある程度経験を積んだエンジニア相手なら説明を省略できる。「こういう手続きをやってデータがこうなってて」と全部説明しなくて良い。深く考える必要もない。コミュニケーションコストの削減が大きいメリットである。

(2)課題

「オブジェクト指向は後発ゆえ、デメリットは特にない」という説もある。これは少し違うのではないかと思う。

オブジェクト指向には3つの課題があると考えている。

  • ア 複数オブジェクトの扱いに弱い
  • イ メッセージが見えない
  • ウ レイヤ構造から逃げられない

2つは理論上の欠陥、1つは情報という世界の本質的な構造に由来する問題だ。前者は未解決だが、後者には暫定的解決策がある。

ア 複数オブジェクトの扱いに弱い

まず第一のオブジェクト指向の限界として、2つ以上のオブジェクトを操作できないというのがある。

あるオブジェクトに「鳴け」と命令して「ニャー」とログを出させるのは簡単だ。では、オブジェクトが2つ以上になった場合はどうだろうか。

現実世界で「並べ」と言ったら生徒が並ぶ。しかし、プログラムの世界で「並べと言うだけで生徒を並ばせる」ことはできない。全員に「座標(x, y)に移動しろ」と耳打ちして回るしかないのだ。

まるで幼稚園児と先生のようだ。

このように、「先生」に当たるオブジェクトに「生徒」全員を登録してやらなければならない。現実と大きく違う点の1つはここだ。オブジェクト指向という道具の難しさはこういうところである。

イ メッセージが見えない

第二の問題は、渡されるデータが見えないことだ。オブジェクト指向は本来、オブジェクト同士がメッセージをやり取りして「あたかもモノ同士が会話しているかのように振舞わせる」という設計だった。

下に図を載せた。左が理想で、右が現実だ。

現実には右のようにメソッドを呼び出す。オブジェクト間でやり取りされるメッセージは、自然界であれば第三者が観測できる。情報の世界ではそうではない。盗聴は出来ないし、またそもそも普通のC#であればメッセージ自体が実装されていない。

情報の世界は捨象されているので、完全に自然を模倣することは出来ない。よくある「オブジェクト指向を動物で例えた説明」の違和感はこれだ。そもそもプログラムで犬や猫を作れるわけがない。

機械と人間は根本的に異なる。機械の本質は捨象だ。この根本的な違いのせいで、捨象されたメッセージという概念の理解が難しくなっている。これはプログラミングの難しさの本質でもある。メッセージという仕組みが今後使われるようになるかは分からないが、出来れば改善した方が良い課題の一つだろう。

ウ レイヤ構造から逃げられない

第三の問題として、オブジェクト指向はレイヤ構造を併用する必要がある。冒頭で過去のパラダイムを紹介する際「ノイマン型コンピュータ」のところで、情報における「レベル」の話をしたが、同一アプリケーションの中で、同じ言語なのに実装のレベルが異なるパターンがある。

それは、ネットワークやデータ(ローカル)が設計に入る場合である。目に見える部分と見えない部分のレイヤーは明らかに違う。

目に見える部分と見えない部分の処理を同じ処に書くのはアンチパターンの一つなので、サーバにデータを送る処理と、描画を行うスクリプトは分けなくてはならない。

よくLogin ControllerというコードにGUIの処理と、データ部分の処理をまとめて書いている実装を見かける。これは良くない。目に見える部分とデータ部分(=見えない部分)はレイヤが違うので、階層的な配置を上手く考える必要がある。

下図も完璧な例ではないのだが、Unity(C#)において、データ+通信のロジックと、目に見える部分の処理を分離するという意味では良いやり方だ。目に見える「View」の部分、ローカルのメモリやストレージ上のデータを扱う「Database」の部分、サーバと通信を行う「System」レベルで明確に実装を分けるという理屈である。
image.png

「オブジェクト」の説明で、ネットワーク上をやり取りされるデータもモノだという話をした。データは構造体(クラス)で定義しておくと後々楽なことが多い。

ネットワーク周りも、出来ればこのように各オブジェクトに勝手に通信させるのではなく

このように、ネットワークオブジェクトを通じて情報を送るのが良いだろう。

NOはネットワークオブジェクトの略だ。通信に使われるソケットと回線は通常1組だからだ。

通信が絡む場合に大事なのはレイヤを分けることだ。

複雑で大規模なアプリケーションはレイヤを積み上げることで作られる。奇跡的にたまたま上手く積み上げられても、そのようなアプリは保守段階で瓦解するだろう。とくにデータとネットワークが絡む場合は、レイヤ構造を意識しないとならないケースが多い。

MVCとかMVPという言葉を聞いたことはないだろうか。それはこの「レイヤアーキテクチャ」の話だ。その果てにはMVVMとかクリーンアーキテクチャまで様々な概念が存在する。オブジェクト指向はこれらのレイヤアーキテクチャと組み合わせる必要がある17

オブジェクト指向を学んで終わり、ではない。ソフトウェア開発は考えることが多い。これが第三の課題である。

9.総括

「オブジェクト」とはあらゆるモノのことである。3Dの物体だけでなく、GUI1つ1つ・キャラクタやドアなどの物体、果てにはオブジェクト同士で受け渡しされるデータやファイルまでもがオブジェクトである。

これらのオブジェクトを一つ一つ作って完成させたものがオブジェクト指向の世界のシステムであり、仕様変更に極めて強い。

他方、オブジェクト指向には「コピペ機能である」「犬や猫を表現するためのものである」など、いくつかの誤解と躓きやすいポイントがある。複雑なアプリケーションにはオブジェクト指向以外の考え方も組み込んで設計する必要があるなどの課題もあり、オブジェクト指向の概念の一つである小単位でのモジュール化についてはまだ十分に活用されているとは言えない。

オブジェクト指向の利益はあまり享受されているとは言えず、その概念もあまり浸透してはいない(バズワードとして知っている人は多い)ので、今後は正しい設計の出来るプログラマが増え、プログラミング環境が進歩することを願うところである。


ご指摘・ご質問はコメント欄に頂けますと幸いです。

  1. 実際の動機は goto を無くそうというところや、ロジックの抽象化というところにあったようだが。しかし、実用主義的に解釈をするとif/forの実装こそが構造化プログラミングの本質であったと言える。

  2. 人間、誰しも心に内なるひろゆきを秘めているものだが、「それってfor文でも書けますよね」などと言うのは野暮である。

  3. 高名な学者さんが作られたので「あほらしい」などと言ってはならない。

  4. 役に立つ度合いは定量的でないので注意

  5. 『Unityで始めるゲーム作り』のサンプルプログラム

  6. ネタバレとはけしからんじゃないか!

  7. UnityだとGameObjectを作るだけでモノが出来てしまうために混乱しやすいが、GameObjectは名前通りObjectの一種だ。「一種だ」というのは、オブジェクトには必ずしもヒエラルキー上に実体が無くても動作するものがあるため。

  8. フィールドが長いのはそれほど大きな問題ではない。他の技術的課題は以下。
    ① そもそもこれはMVCモデルならModelにあたり、Controllerではない。
    ② staticで良いものをわざわざdynamicに記述している(こちらはMonoBehaviourの仕様のせいなので仕方ないといえば仕方ない)。
    ③ レイヤを分けるべき処理を全部一緒くたにまとめて書いている。

  9. コメントでご指摘いただきました @shiracamusさんありがとうございます。

  10. 正しくはインターフェイス

  11. 『オブジェクト指向と10年戦ってわかったこと(Qiita)』から引用 2

  12. 『カプセル化=データの変更制限ではない(オブジェクト指向プログラミングを極める)』から引用

  13. プログラミングの法則の1つ。ほかの原則は
    「DRYの原則」
    「YAGNIの原則」
    「PIEの原則」
    「コマンドとクエリの分離原則」
    「求めるな、命じよ」
    「最小知識の原則」
    等がある。

  14. ここで言う「処理してもらう」とはオブジェクト指向における「委譲(Delegation)」とかいう難しい話ではなく、"単に処理を投げつけておけば楽" というレベルの話である。C#における delegateLambda 式に取って代られており、この議論は旧時代のものとなりつつある。

  15. 残念ながら、文献によってその個数も多態的である。 カーデリとウィグナーの原典((PDF)On Understanding Types, Data Abstraction, and Polymorphism)には、ポリモーフィズムは4つの要素から成ると書かれている!

  16. ドメイン駆動設計とは何なのか? ユーザーの業務知識をコードで表現する開発手法について|CodeZine」より

  17. 【プログラミング】MVC,MVPを理解するためのレイヤーアーキテクチャ」を参照

230
190
26

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
230
190

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?