オブジェクト指向(object oriented)とは、1980年代以降ずっと主流を占めているプログラミングパラダイムである。
オブジェクト指向とは、手続きをオブジェクト(対象)を単位として考えることによって、「対象を(が)〜する」という人間の思考に近い形でプログラミングしようとするプログラミングパラダイムである。
C言語を代表格とする手続き型言語の欠点を修正するような形で1980年代に普及した。C++はその普及のさきがけであるが元祖ではない。普及以降、関数型プログラミング派からは異論があるかもしれないが、2021年現在においても主流のプログラミングパラダイムである。
オブジェクト指向プログラミングで中核をなすのが型理論に基づくクラスの概念である。オブジェクト指向の三大要素とされるカプセル化・継承・多態性もクラスの性質に関するものである。
クラスはオブジェクトが保持するデータ構造とそれに対する操作をまとめたもので、共通する性質を持つオブジェクトをひとまとめにする働きがあるのだが、長くなるので詳しく知りたい人向けには後述することにする。
細かいことを言えば、プログラム実行時にクラス構造を変更できない言語(クラスベース)と、変更をできる言語(プロトタイプベース)が存在するが、プログラミング中級者までは気にしなくてよい。中には、クラスというものを排除してオブジェクト指向を実現した言語(SelfやLENSなど)も存在するが、プログラミング言語マニアでもない限り忘れてよい。
オブジェクト指向は主流かつ人気のプログラミングパラダイムであり、大抵の実用的プログラミング言語で採用され、各種解説書でも推奨されている。
しかし、実際にはオブジェクト指向はプログラミング言語毎、さらには解説者毎にアレンジを受けており、言語・解説者によって意味するところは異なっている。つまり、オリジナルとされる定義や上記三大要素のように一定の支持を得ている定義もあるにはあるが、オブジェクト指向に決定的な定義はないとも言える。
だが確固とした定義がないがゆえに、「はっきりわからないけど、従えば万事うまくいくものがオブジェクト指向のはず」
といった逆転的論理によって、オブジェクト指向を神格化してしまってはいないだろうか。
大まかな傾向は一致していても詳細な定義が人それぞれである以上、うまく行かない場合があるのは当然のことであり、「オブジェクト指向ならうまくいくはずだから、うまくいかないのは自分がオブジェクト指向を理解できていないせいだ」といった卑屈な思考にとらわれる必要はない。
自分の信じるオブジェクト指向でうまく行かない時に、立ち止まって他の考え方に視野を広げればよいだけの話ではなかろうか。オブジェクト指向は目的ではなく、あくまでもうまくプログラミングするための手段なのだから。
以下ではプログラミング未経験者でも読めるように、出来るだけ特定のプログラミング言語に依存しない記述を試みるが、長いので下記を参考に希望のレベルまで読み進めること。
あなた(プログラマー)が動物園の飼育員(コンピューター)の監督に就任したとしよう。あなたは飼育員に対して飼育マニュアル(プログラム)を通じて指示することしか出来ず、自ら動物と触れ合うことは出来ない。
説明のための例示であり、実在の動物園・飼育員・監督・動物等及びその動作とは一切関係ない。異論は認めない。
あなたは思考を放棄してすべての手順(手続き)を逐一書き出すことにした。
飼育員の業務
虎Aに対し肉10kgを用意する。
虎A用の肉10kgを1kgごとに切り分ける。
虎Aの檻に切り分けた肉を置いてくる。
虎Aは満腹になる。
虎Bに対し肉10kgを用意する。
虎B用の肉10kgを1kgごとに切り分ける。
虎Bの檻に切り分けた肉を置いてくる。
虎Bは満腹になる。
虎Cに対し肉10kgを用意する。
虎用の肉10kgを1kgごとに切り分ける。
虎Cの檻に切り分けた肉を置いてくる。
虎Cは満腹になる。
上記でもゲシュタルト崩壊して十分読みづらいが、さらに虎の数が増えた時に収拾がつかなくなることは想像に難くない。読みづらいと間違いにも気づきにくくなる。「虎C」と書くところを「虎」としてしまっていることに初見で気づいた人はどれくらいいただろうか。
オブジェクト指向を導入すると、上記のマニュアルは以下のようになる。
虎
餌のやり方
虎に対し肉10kgを用意する。
虎用の肉10kgを1kgごとに切り分ける。
虎の檻に切り分けた肉を置いてくる。
虎は満腹になる。飼育員の業務
虎A, B, Cに餌をやる。
業務内容をA, B, Cという対象(object)を単位として整理するという方針に従えば(oriented)、A, B, Cは「虎」に分類(classify)されてひと括りに扱うことができるようになり、 飼育員のすることは変わっていないのにマニュアルの見通しが良くなったのである。
これなら虎の数が少々増えても読みづらくはならない。また、現実の監督が飼育員に指示する時もBeforeよりAfterのやり方になるであろうことから、より人間の考え方に近づいているということもおわかりいただけただろうか。
オブジェクトには「対象」以外に「物体」という訳語もあるが、スレッドやHTTP接続、果ては関数など形のないものまでオブジェクト(対象)化できるので、オブジェクト = 物体 という考え方に囚われるとよろしくない。
さらに厳密に言うと、オブジェクトは対象の状態を記録したプロパティを束ねたデータの集合体であり対象そのものではない。冷静に考えれば虎「A, B, C」は、それぞれにつけられた名前であって虎そのものではないのだから当然なのであるが、これを忘れると後で虎が異次元空間に消えたり、何もないところから突然現れたりすることになる。
後になって、虎の餌用の肉を1kgではなく0.5kgごとに切り分けなければならないことが判明したとしよう。オブジェクト指向導入前であれば虎A, B, Cのそれぞれについて全3ヶ所の変更が必要であったが、オブジェクト指向導入後であれば「餌のやり方」の項を1ヶ所変更するだけで済む。
虎Dが増えたとしても、オブジェクト指向なら「虎A, B, C」を「虎A, B, C, D」とするだけである。
このようにオブジェクト指向が導入されれば状況の変更にも柔軟に対応できるようになるのである。
例えば上記の「虎」の章の部分の執筆を部下に任せることができる(部下がいるほど偉ければだが)。
一旦任せてしまえば、たとえば先述の肉の切り分け単位が変わったケースでもあなた自身が対応をする必要はない。
また、任された部下が、虎のプロパティに空腹か満腹かを記録する代わりに、その日に食べた肉の量を記録して、10kgに満たなければ空腹であると判断するようにしても、あなた自身はそれを気にする必要はない。
このように、内部実装を外部から見えないようにする(見なくて良いようにする)ことを、クラスをカプセルに見立ててカプセル化とか実装の隠蔽などと呼ぶ。
カプセル化により、あなた自身は「虎」の細かいことは気にせずに「飼育員の業務」の章の執筆に専念することができる。また、「虎」の章だけを部品のように「動物園」から持ち出して、「サーカス」や「アフリカの動物保護施設」のような別の施設で使ってマニュアル執筆の手間を大幅に省くことも夢ではない。
虎DではなくサイE, Fが増えたとしよう。以下のような対応が可能である。
動物
餌のやり方
動物に対し餌を用意する。
動物の檻に用意した餌を置いてくる。
動物は満腹になる。虎
虎は動物である。
餌の用意
虎に対し肉10kgを用意する。
虎用の肉10kgを1kgごとに切り分ける。サイ
餌の用意
飼育員の業務
A, B, Cは虎である。
E, Fはサイである。動物A, B, C, E, Fに餌をやる。
「虎」と「サイ」の共通点である「動物」という性質(分類)を記述することにより、虎とサイについては相違点である餌の用意方法だけを記述すれば済むようになっている。そして、餌をやる時も「虎」と「サイ」について別々に記述せず「動物」とひと括りにして餌をやることができるのだ。
「虎」と「サイ」よりも「動物」の方が抽象的なので、抽象化と呼ぶこともある。実際のプログラミングでは「動物」のような分類ではなく、「餌を食べるもの」「歩くもの」のような抽象化を行うことも多い。
サブクラスのインスタンスは必ずスーパークラスの性質を備えているべきであり、スーパークラスのインスタンスと置き換えて使用することができるという「継承」の原則論。
上記でいうなら「動物X」と書かれているところがあれば、「虎A」や「サイF」で置き換えてもマニュアルとして意味不明や実施不可能にならないということ。
これを満たさない継承はおそらくまともに動作しないのでやってはならない。
プログラミングはコーディングよりもデバッグの方が大変だという話もあるくらいで、ミスによるバグをなくすというのもプログラミング言語やパラダイムの大事な役割である。オブジェクト指向により手続き型プログラミングと呼ばれる旧来のスタイルよりもバグは大幅に減ったが、それでも問題点を完全になくすことはできなかった。概要で述べたようにオブジェクト指向は全てを解決する銀の弾丸ではない。
以下ではオブジェクト指向が批判される原因となるミスについて、上記の例を引き継いで解説しようと思う。単純化して説明するので「そんなミスする奴いねーよ、バーカ」とか思ってしまうかもしれない。しかし、日々繰り返したり、他のことと組み合わさって複雑になったりすると実際に起きてしまうのだ。
以下のルールを追加する。
なお、虎が死亡するとあなたは監督責任を問われてクビになる(プログラムの異常終了)。
動物園は発展し、あなたは出世して部下に各動物のマニュアル作成を任せ、自身は「飼育員の業務」の章の執筆に専念できるようになった。そんな折、「猛獣ショーをやるぞ」という園長の掛け声のもと手順書が以下のように変更された。単純化のためサイは来なかったものとする。
飼育員の業務
ところが、猛獣ショー開演当日の閉園後、虎Cが死亡したという報告が届き、あなたはクビになった。
実は部下に任せていた「虎」の章は以下のようになっていたのだ。
虎
虎は空腹になったり満腹になったりする。
虎は、満腹時に餌を与えると過食により死亡する。餌のやり方
虎に対し肉10kgを用意する。
虎用の肉10kgを1kgごとに切り分ける。
虎の檻に切り分けた肉を置いてくる。
虎は満腹になる。猛獣ショー
虎Cは猛獣ショーでご褒美をもらって満腹になっており、この状態で虎A, Bと共に餌を与えられたため過食により死亡したのだ。
このような不幸な事故が起こった原因としては色々考えられるが、一つはマニュアルにおいて虎Cと書かれているだけでは虎Cが空腹なのか満腹なのかわからないということが挙げられる。つまり虎Cというオブジェクトに空腹という状態と満腹という状態両方が存在しうることが問題なのである。
またカプセル化により「猛獣ショー」のメソッドの中身を意識しなくなったことも原因の一つといえるかもしれない。
この問題を回避する方法は一通りではないのと、方法(参照透過)によっては空腹な虎が異次元空間に消えたりするのでここではこれ以上深入りしない。
オブジェクト指向ではオブジェクトの状態が変化していくことが前提なので、参照透過とは相性が悪いとされている。
抽象化・共通化は利点であるが、共通化した部分に個別に変更する部分が生じた場合、また非共通化することになる。
サイが登場した例に戻って説明する:
虎は肉食獣なので逃走のリスクを下げるため、虎の檻だけ扉を二重にしたくなったとする。檻の出入りは「動物」のメソッドで共通化しているので、虎の檻の出入りの手順だけを変更しようとすると、せっかく共通化した「檻の出入り」の部分を「虎」と「サイ」で別々に書き直さなければならない。別々に書いてあれば「虎」の変更だけで済んだのに、「動物」「虎」「サイ」の3ヶ所に変更が生じてしまう。
継承によるプログラムの部品化はオブジェクト指向の特色とされている。確かに、各プログラミング言語の標準ライブラリレベルで十分な時間と人手をかけて検討されテストされた継承であれば、オブジェクト指向の利益を最大限に享受できるだろう。しかし、末端のプログラマーがその場の思いつきで共通部品化したものについては継承により予期せぬ不具合を引き起こすと言われており、言語仕様上は継承をサポートしていても積極的に継承を利用することは勧めていない場合もある。
状態変化による悲劇を避ける方法の一つに、状態変化を禁止してイミュータブルとし関数型言語による宣言型プログラミングを行うという方法がある。
掲示板
79 ななしのよっしん
2024/06/06(木) 20:14:08 ID: otQGgfdDIu
記事の例にある虎の処理の共通化は手続き型でも普通に出来る気がするな
データと処理がそれぞれレコード(構造体)とプロシージャ(手続き)に分かれてるだけで、虎ABCをレコードとして一般化して対応するプロシージャで処理すればいい
これをモジュールとして纏めてレコードの内訳を非公開にすればカプセル化も実現できる
オブジェクト指向に特有の利点は継承を使って後付け拡張と分業をしやすいとこだと思う
手続き型で記事にあるサイの追加に似たことをやろうとすると共用体や手続き型変数を使う必要が出てくるが、共用体はトップレベルの変更が必要で分業に不向き、手続き型変数は実行時に決まるから静的チェック出来ない不安がある
ただ記事でも触れられてるみたいな変更で起きる副作用とかの使いづらさもあるし、継承が深くなると急激に可読性が下がるので、少なくとも初心者は使わない方がいいと思う
80 ななしのよっしん
2024/08/20(火) 23:44:09 ID: eBk5qNbmei
会社をプログラミングする場合に例えると、まず「営業」というプログラムを書く。これがクラス。
営業クラスに直接指示はしない。「営業のスティーブ」「営業のビル」「営業のラリー」を生成する命令を始めに書いて
あとは「スティーブ、ビル、ラリー」に営業の仕事を指示する。「スティーブ、ビル、ラリー」がオブジェクト。
3人にはそれぞれ別の仕事を指示していい。
その後は「経理」「製造」「社長」などのクラスも書いていってプログラム全体を仕上げる。
81 ななしのよっしん
2024/11/27(水) 22:58:28 ID: QaDxJ3dTMM
>>79
「オブジェクト指向」と「オブジェクト指向言語」は微妙に違うのだ。普通の構造化言語でもOOPはできるし概念的には重なってる。処理をオブジェクト的に整理してけば結局OOPのエミュレートになるわけで
ただOO言語はOOP前提で作られてるから継承なんかがネイティブ文法になっててより自然に書けるっていうのが違い
提供: @Diavolo
提供: キャバ嬢の着エロアイドルごっこ
提供: yui@柱島
提供: 狩猫
急上昇ワード改
最終更新:2025/04/12(土) 07:00
最終更新:2025/04/12(土) 07:00
ウォッチリストに追加しました!
すでにウォッチリストに
入っています。
追加に失敗しました。
ほめた!
ほめるを取消しました。
ほめるに失敗しました。
ほめるの取消しに失敗しました。