Last Updated: 2/6/2024, 5:44:57 AM

# オブジェクト指向ってなに?

名前空間を適切に分けること

ここでは簡単に「オブジェクト指向は名前空間をわけること」というざっくりした考え方を示します。 その上で「継承よりも合成」に理解を繋げることを目的にしています。

また、ここでのオブジェクト指向とは、 以下の記事における ② 「クラスによる抽象データ型の実現(カプセル化・継承・多態性)」を指しているものとします。

このが、ものすごく参考になりました。

オブジェクト指向とは何ですか? - オブジェクト指向をきちんと説明することをまだ諦めていない (opens new window)

①も②も同じように「オブジェクト指向プログラミング」を名乗ったため、 OOPというコンセプトは現在に至るまで混乱を極めています。

これは私見ですが、もし、①が最初にオブジェクト指向などと名乗らず「メッセージ指向」と、 and/or 続く②が「クラス指向」等々と名乗ってくれていたならば、 今日のような混乱や「どちらが真のオブジェクト指向か?」 などという不毛な論争の多くは避けられていたはずです。

オブジェクト指向とは何かについて考えるために、 まず「どんな時に関数で書いて、どんな時にメソッドで書くべきなんだろう?」 ということを皮切りに考えていきたいと思います。

# メソッドと関数の使い分け

どんな時に関数で書いて、どんな時にメソッドで書くべきなんだろう? ということについて、3ステップで考えていきます。

# Step 1. クラスに限定されているかでクラスを設計する。

答え: まずは、次のような理解でいいかなと思います。

Pythonの関数とメソッドの違いについて - teratail (opens new window)

  • メソッド
    特定のクラスに限定された操作
  • 関数
    特定のクラスに限定されない操作

例えば list.sort メソッドと sorted 関数があります。 なぜ list.sort はメソッドで sorted は関数なのでしょうか?

実は sorted 関数は、for 文の in の中に書けるオブジェクならなんでも引数に取れます。例えば dict, list, str などです。 反対に list.sort メソッドは list しか引数に取れません。

lst = [3, 1, 2]
dct = {'a': 0, 'b': 1, 'c': 2}
txt = 'Hello, world!'

# sorted は何でも
sorted(dct)
sorted(txt)
sorted(lst)

# sort は list だけ
lst.sort()
lst
>>> # sorted は何でも
... sorted(dct)
['a', 'b', 'c']
>>> sorted(txt)
[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
>>> sorted(lst)
[1, 2, 3]
>>> 
>>> # sort は list だけ
... lst.sort()
>>> lst
[1, 2, 3]
>>> 

list.sort() メソッドはリストにのみ定義されています。 一方 sorted() 関数は任意のイテラブルを受け付けます。
Python HOWTO - ソート HOW TO (opens new window)

結果をリストに代入しなければなりませんが、list の場合は最初からあるリストに、結果を代入すればいいだけです。 一方で dict, str は、結果を保存するための新しいリストを生成しなければなりません。 その分だけメモリを消費してしまいます。

import sys

lst1 = [3, 1, 2]
lst2= sorted(lst1)

# sorted は新しいオブジェクトを生成している。
lst1 # [3, 1, 2]
lst2 # [1, 2, 3]

# その分、メモリを消費する
sys.getsizeof(lst1)  # 88
sys.getsizeof(lst2)  # 112

# なぜ要素数が同じなのに
# メモリは 88, 112 と結果が異なるのかは、わかりません。
# NG
# エラーになります。
lst = [0, 3, 1, 2]
for element in lst.sort():
    element
# OK
lst = [0, 3, 1, 2]
lst.sort()
for element in lst:
    element

パフォーマンスが問題となる状況では、ソートするためだけにリストのコピーを作るのは無駄が多いです。 そこで、 list.sort() は新しいリストを生成しないで、リストをソートします。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問 (opens new window)

このことを忘れないため、この関数はソートされたリストを返しません (ワイの注釈: list.sort が新たにリストを生成しないということを忘れないために、list.sort は None を返します。)。 こうすることで、ソートされたコピーが必要で、ソートされていないものも残しておきたいときに、 うっかり上書きしてしまうようなことがなくなります。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問 (opens new window)

# Step 2. 現実世界のモデリングとしてクラス設計をする。

「メッセージ」という言葉があります。ちょっと難しいですが、 頭の片隅にいれて置くと、オブジェクト指向でコーディングをする上で、 関数とメソッドを書き分ける上で、いつか理解の助けになるかもしれません。

この記事は、古くかつ Java を題材にしていますが、とてもいい記事だと思います。

オブジェクト指向は、基本的に「現実世界をモデリングして、 そのモデルのコードを書いたらプログラムができる」ということを目指して作られたものです。 (実際にシステムを作るとモデルと現実世界は必ずしも一緒じゃありませんが)

ですので、オブジェクト指向を考えたお偉いさんが、現実の世界でおこっているある状態をモデル化するときに 「仕事をお願いする」というようなことを「メッセージ」という名前をつけました。
メッセージ脳の作り方 - オブジェクト脳オンライン (opens new window)

この記事も Java を題材にしていて、かつメッセージという言葉はでてきませんが オブジェクト指向を理解する上において、すごくいい記事だと思います。

もう一度言う。オブジェクトはデータとそれを操作する関数をセットにしたものではない オブジェクトはデータエンティティではない。 では何か? ... 真のオブジェクト指向の世界では、オブジェクトを生きた有機体のように扱い、 オブジェクトには生まれた日と死ぬ瞬間がある。 また、君が望むなら、アイデンティティや性質を持たせてもいい。 犬にはデータの一部(例えば体重)をくれるよう頼むことができるし、犬はその情報を返してもよい。 ただ、この犬は能動的なコンポーネントであることを忘れてはいけない。 こちらの要求に対し、何をするかは犬が決めるのだ。
Getter/Setterは悪だ。以上。 - To Be Decided (opens new window)

最初にオブジェクト指向について習うときは、ほとんど聞きませんが、メッセージングという言葉は、とても大事な言葉なようです。

Eclipseを開発したDave Thomasや、オブジェクト指向という言葉の生みの親であるAlan Kay博士は、 オブジェクト指向という言葉は失敗だったと語っている。[1] これは、本来オブジェクト指向が重視すべきは「オブジェクト」ではなく「メッセージング」であるにもかかわらず「メッセージング」がおろそかにされているためである。
オブジェクト指向 - Wikipedia (opens new window)

# Step 3. コードの整理術としてクラス設計をする。

ただ、以下の本では、オブジェクト指向は、必ずしも現実世界のものを表現しているという訳ではなくて、 単純にコードを整理するための方法論、「整理術」として捉えるといいですよね的な話をしてくれています。

結局「オブジェクト指向」という難しい言葉については考えずに、関数で書いた方が読みやすければ関数で メソッドで書いた方が読みやすければメソッドで書けば、いいかなと思ったりもします。

この章では従来からしばしば行われてきた「オブジェクト指向が現実世界をそのままソフトウエアに表現する技術である」という説明について 考え直します。 結論から言うと、オブジェクト指向プログラミングの仕組みと現実世界の様子は、それなりに似ている点はあるものの、実際はかなり違うものです。

... かなり中略

筆者も長い間、コンセプトが中心だと考えていましたが、 業務分析や要求定義でオブジェクト指向のコンセプトを現実世界に当てはめ、 それをC++やJavaで実装しようと試みるうちに、 このコンセプトが当初期待していたほど現実世界のモデル化やソフトウエアの設計にうまく適合しないと感じるようになりました。 このコンセプトを当てはめることにこだわりすぎて、 かえって保守しづらいアプリケーション構造にしてしまうような事例もいくつか見てきました。

オブジェクト指向でなぜ作るのか - 平澤 章 (opens new window)

上記の書籍では、"唯一の正しい" クラス設計とは何か?について突き詰めて考えて、ゲシュタルト崩壊していく感覚を示してくれています。 オブジェクト指向の限界を論理的に説明するのは、おそらく無理だと思うのですが、自分にはなんとなく腑に落ちる説明でした。

クラス設計は、視点や立場によって変わるので、全てのシステムに適用できる "唯一の正しい" クラス設計がある訳ではないということかなと思っています。 だから「オブジェクト指向」は "唯一の正しい" というよりは、読みやすい、理解しやすいくらいの「整理術」として抑えておくのが良いということを、 上記の書籍は伝えようとしてくれるのかなと思いました。

オブジェクト指向だけでなく、立場(視点)が曖昧な議論はえてして混乱を招くと思う。 余談だがデザインパターン全盛の時代に、あるべきクラス設計の議論でモメて時間をつぶすという事態は結構な割合で経験者がいると思う。
icoxfog417 - Twitter (opens new window)

すべての現実世界のものはクラスにモデリングできると考えて設計された Java では、 全ての関数は必ずクラスに所属していなければなりませんでした。

// Java
public class Sample{
    public static void main(String args[]){
    	printSum(3, 4);
    }
    public static void printSum(int a, int b){
    	System.out.println(a + b);
    }
}

しかし、それでは極端すぎるので Java の後継言語たる Kotlin では、 クラスに所属する必要はなくなりました。

// Kotlin
fun main(args: Array<String>) {
    printSum(3, 4)
}

fun printSum(a: Int, b: Int) {
    println(a + b)
}

全てのものを正確にモデリングすることはおろか、 全てのものをモデリングすることそのものが困難だったという訳です。 と個人的に思い込んでいます。

オブジェクト指向とは、ごくごくざっくり言えばクラスを使って「名前空間」を適切に分割することです。 その1つのやり方として「現実のものを元にクラス設計をする」こともあるけど、 必ずしもそうではないということかなと個人的に思い込んでいます。

# 混乱したこと

Python で書いていて2つの書き方を見て混乱しました。 1つは len, str などの関数、もう1つは str.join メソッドです。

メソッドと関数の書き分けをどうすればいいのかわからなくなりました。 当時の経緯を振り返ることによって、疑問が解けるわけではないのですが、のぞいて見たいと思います。

# 1. メソッドではなく関数が採用された例

関数の方が読みやすいから。 まず第一に、純粋にメソッドよりも関数が読みやすいから。 また第二に、組み込み関数することで、誰が len を書いても len がオブジェクトの個数であることを明示できます。

全ての処理に対して組み込み型を定義することはできませんが len などのよく定義される処理については、 関数として定義しても良いのかなと思ったりもします。

# (本物) 
len(obj)
# (偽物)オブジェクト指向なら
# メソッドで統一してほしいのになんで?
obj.len()



# 2. 関数ではなくメソッドが採用された例

文字列を結合するという処理が  組み込み関数  にするほどよく使うものではないし、  標準ライブラリ  string に押し込むほど使わないわけでもないから。 結果、折衷案をとって  組み込み型  str のメソッドになりました。

# 方法1
#   本物
#   str のメソッドにする。
', '.join(['Hello', 'wolrd!'])
# 方法2
#   偽物
#   list のメソッドにする。
['Hello, world!'].join('')
# 方法3
#   偽物
#   組み込み関数にする。
join(['Hello', 'world'], ', ')
# 方法4(偽物)
#   偽物
#   標準ライブラリ string の関数にする。
import string
string.join(['Hello', 'world'], ', ')




# オブジェクト指向とは、なにか?

# Step 1. オブジェクト指向三大要素

この用語については、ここでは解説はしません。 自分は オブジェクト指向でなぜ作るのか第 2 版(平澤章) で勉強しました。

オブジェクト指向と10年戦ってわかったこと (opens new window)

オブジェクト指向三大要素ってありますよね。 オブジェクト指向は「カプセル化」「継承」「ポリモーフィズム」の 3 つの要素で成り立つと言われます。

# Step 2. 実装による言語の分類

「オブジェクト指向」は、次の4つに大きく分類されるそうです。 Python は、この分類の中だと Python は「オブジェクト指向」にはいります。

オブジェクト指向は4つある - オブジェクト指向と20年戦ってわかったこと (opens new window)

オブジェクト指向言語によって、微妙に考え方とか、理想の形式とかが違っています。 「どの言語でも通用するオブジェクト指向のイデア」というものがこの世にあるとしたら、 という前提でよく話がされるんですが、基本的にそんなものはほとんどないと思っています。 だいたい4系統に類型化できるんじゃないかなって考えています。 オブジェクト指向言語によって、何に重きを置いているかが違う気がします。

  1. クラス指向
  2. インタフェース指向
  3. オブジェクト指向
  4. メッセージパッシング指向

どうとらえるかの解釈だけの違いだと思うのですが、 「オブジェクト指向」は、「言語の実装の仕方」を指した用語ではなくて、 「コードをどうやって書く」か、について書かれたものではないのかなと感じたりもします。

オブジェクト指向(オブジェクトしこう)とは、 オブジェクト同士の相互作用として、 システムの振る舞いをとらえる考え方である。
オブジェクト指向 - Wikipedia (opens new window)

極論すればどんな言語だって、 それと同じ機能が実装できてしまうからです。 上の文章で言えばクラス指向の C++ で、 メッセージングパッシング指向の Erlang と同じことができないわけではないからです。 厳しいとは思うのですが。

もちろん「言語の実装の仕方」によって、 「コードの書き方」がある程度定まってくる部分もあると思うのですが。 ネット上で多くの方が、そして自分も含めて、頭を抱えているのは、 「オブジェクト指向」での書き方の部分ではないのかなと思ったりします。

GIL のある Python よりも、 Erlang の方がよりメッセージ指向に近いということも、 もっともだと思います。

ただ、オブジェクト指向のイデアはなくても何か共通するものがあるはずで、 いったい何で C++, Java, Python, Erlang がオブジェクト指向という枠で 一括りにされているのか、を知りたいのかなと思ったりもします。

そのため上記の記事も「オブジェクト指向」が4分類できるのではなくて、 「オブジェクト指向を支援する言語の実装の仕方」は4分類できる、 と捉えておいた方が、もしかしたら誤解は少ないかなと思ったりします。 言語を分類する視点として、とても勉強になったのでご紹介させていただきました。

# Step 3. Java のセールストーク


OOP は無駄を省いて整理整頓するプログラミング技術

オブジェクト指向でなぜ作るのか第 2 版(平澤章)

まず、はじめて Java を習った時に、class を使うプログラミングは 全てオブジェクト指向だと思っていました。 しかし、しばらくするとクラス機能とオブジェクト指向は、 どうやら別物であることに気づきました。 オブジェクト指向は言語仕様の話をしているのではなく、 プログラミングの書き方について話していることに気づいたのです。

次に、あらためてオブジェクト指向とはなにかを考えた時に、 オブジェクト指向の3大要素である「カプセル化」、「継承」、「多態性」というキーワードについて考えました。 しかし、この3つを合わせて何か1つのコンセプトが生まれてくるわけではありませんでした。

クラス定義文、あるいはクラスという機能を使ってオブジェクトを定義した時に 派生的に生まれる機能ではありますが、 その逆から考えてもクラス機能が出てくることがあっても、 何か有益なコンセプトが1つパッとは思いつきませんでした。 それはちょうどクラス機能を使えば、オブジェクト指向だとは言えないのと同じように。

最後に「オブジェクト指向」あるいは「メッセージ」という言葉から入って考えても、 やはり物ではないという記事や、あるいはそう言った記事が炎上するのを何度も見かけました。 おそらく違うのだろうと思いましたし、実際、クラスを定義する時に オブジェクトってなんだ?って、疑問に思いながら定義していました。

オブジェクト指向が何かわからないし、でもかと言って無いと困る。 見えないのに、確かに存在する。 自分がオブジェクト指向らしいことをやっている。 でもそれが何かわからない。 自分は一体何をやっているんだみたいな気分です。

しかし、ある日、「オブジェクト指向でなぜつくるのか」という本を読んで、 「オブジェクト指向はコードの整理術」という文言を見てやっと腑に落ちました。 オブジェクト指向とは、いい感じに名前空間を分割して self に叩き込んでるだけだったからです。 それをカッコよく「カプセル化」と呼ぶのかもしれません。

それでも散々悩んだ挙句、悩みの原因が Java のセールストークだったなんて、 なんだか肩透かしを食らってしまいます。

Javaのセールストーク - オブジェクト指向にメリットなんて存在しない (opens new window)
そもそもどうしてこんなことになったんだ。

Javaというプログラミング言語をご存知だろうか。かつて世界を支配し、一時代を築き上げたプログラミング言語である。 今はもう死体だが。いやAndroidでかろうじて生き残ってるのか。

当時のJavaは、いや今も若干そのきらいがあるが、オブジェクト指向に非常に重きを置いた言語であった。 C++はCで書けるけど、Javaはオブジェクト指向で書かなければならない、 甘えの通用しない言語となっていた(いくつか逃げ道はあったが)。

そんな革新的というか選民思想持ってそうな言語が現れたということは、当然セールストーク祭りが発生するわけである。 「現実世界のモデリングができる」「再利用性が高い」「変更に対して強い」などである。

どっかで聞いたことあるよね。そう、君らが今まで聞かされてきたの、1990年代に使われたセールストークなんだ。 20年前のカビの生えた胡散臭いセールストークを大量に浴びてたわけよ。