プログラミング
なぜオブジェクト指向プログラミング(OOP)が存在するのか
要約
この記事は、オブジェクト指向プログラミング(OOP)の基本的な考え方と、それがなぜ存在するのかをPythonの例を交えて解説しています。現実世界の事象をコードで表現することの重要性、そして「オブジェクト」という概念が、複雑なシステムをより管理しやすく、理解しやすくするためにどのように役立つかを、具体的なコード例と共に説明しています。
全文翻訳
ブログ OOPが存在する理由 2026年8月26日 oop プログラミング python はじめに
この記事では、一般にOOPとして知られるオブジェクト指向プログラミングの核心的な考え方を学びます。
これは、私がPythonでOOPを初めて学んだ何年も前に読みたかった記事です。
OOPが初めての方は、この記事でOOPが存在する理由、その仕組み、そしてPythonでOOPをどのように扱うかを説明します。
もしOOPをすでに知っていると思っているなら、この記事はプログラミングとPythonに対する考え方を変えるでしょう。
コードは現実世界の物事についてです
大学時代にやらなければならなかったプログラミングプロジェクトについてお話ししましょう。それはライブラリ管理サービスを書くというものでした。
私たちはペアで作業することが期待されており、私は親友のTitoとペアになりました。
Titoと私は座って問題文を読み始め、実装する必要があるものを考えました。
Titoが私の方を向いてこう言ったところで、私たちはある地点に達しました。「プログラムで本を表現する最良の方法が私にはわかりません。あなたは検索機能の実装を始めて、私はしばらくこれについて考えてみます。」
検索機能とは、問題文で実装が求められていた一連の関数でした。
find_by_title(catalog, search_term): 指定された検索語を含む本のサブリストを返します
find_by_genre(catalog, genre): 指定されたジャンルの本のサブリストを返します
find_by_author(catalog, author): 指定された著者によって書かれた本のサブリストを返します
私はうなずきましたが、一瞬考えました。
もし私が本の扱いについて何も知らないなら、これらの3つの関数を実装する方法はありません。
Titoは私に同意し、これらの関数を提供してくれると言いました。
book_title(book): 指定された本のタイトルを返します
book_genre(book): 指定された本のジャンルを返します
book_author(book): 指定された著者によって書かれた本の著者名を返します
彼はこれらの関数を、彼が実装する補助関数として考えるように言いました。
私はまだそれらを持っていませんでしたが、彼がそれらを正しく実装してくれると信じて、検索関数を書くことができました。
OOPでは、関連するデータを持つエンティティ(著者、タイトル、ジャンルを持つ本)と、それらのエンティティを操作するための関数のセット(find_by_xxx関数)があります。
さて、少し考えてみてください。
Titoが実装する補助関数を使用して、find_by_title、find_by_genre、find_by_author関数を実装できますか?
どのように進めますか?
私はしばらくそれに取り組み、最終的にリスト内包表記を使用してfind_by_title関数を定義しました。
def find_by_title(catalog, search_term):
search_term = search_term.casefold()
return [ book for book in catalog if search_term in book_title(book).casefold() ]
find_by_title関数は、catalogと呼ばれる本のリストをループで処理し、補助関数book_titleを使用してタイトルを取得します。
その後、casefoldを使用して大文字小文字を区別しない検索を実行します。
ここで注目すべきは、リストcatalogの要素の変数名としてbookを使用していることです。これらの「本」がどのようなものか私にはわかりません!
しかし、Titoがうまくやれば、book_title(book)というコード片は本のタイトルを返し、私のコードは機能するでしょう。
私は同じパターンに従って他の2つの関数も実装しました。
def find_by_genre(catalog, genre):
return [ book for book in catalog if genre == book_genre(book) ]
def find_by_author(catalog, author):
return [ book for book in catalog if author == book_author(book) ]
私は課題の私の部分を終え、Titoに進捗状況を尋ねました。
この課題、Titoと私が作業を分割した方法、そして私が私の部分を書いた方法は、すべて同じことを強調しています。
コードは現実世界の物事についてです。
私たちはライブラリ管理システムを書いています。そのため、本、著者、その他の関連エンティティについて考える必要があります。
ライブラリ管理システムのコードを読んでいるとき、私たちはコードが扱っている本の高レベルの現実世界のエンティティについて推論できることを望みます。
だからこそ、Titoと私は本を表す抽象化を作成することにしました。
これにより、私のコードの部分が大幅に簡略化されます。
プログラムの残りの部分が抽象化を使用できるようにするには、有用な操作を公開するいくつかの関数が必要です。
たとえば、Titoは私が本の情報にアクセスする必要があることに気づいたため、3つのbook_xxx関数を作成しました。
これらの3つの関数は、本の実装方法の低レベルの詳細を隠します。
OOPの核心的な考え方は、それらを扱いやすくするために、関心のあるエンティティに対して高レベルの抽象化を作成することです。
Oはオブジェクトを表します
Titoはしばらくの間、彼の本の表現に取り組み、それから戻ってきました。
彼は「ねえRodrigo、著者、タイトル、ジャンルを受け取り、私が考え出した表現を作成するbook_initialise(author, title, genre)関数も必要だと気づいたんだ」と言って始めました。
そして、彼は私の4つの関数を示しました。
def book_initialise(author, title, genre):
la = list(map(ord, author))
lt = list(map(ord, title))
lg = list(map(ord, genre))
return ((len(la), len(la) + len(lt)), la + lt + lg)
def book_author(book):
return "".join(map(chr, book[1][:book[0][0]]))
def book_title(book):
return "".join(map(chr, book[1][book[0][0]:book[0][1]]))
def book_genre(book):
return "".join(map(chr, book[1][book[0][1]:]))
私はTitoのコードに少し驚きましたが、真実はそれがうまく機能したということです。
book = book_initialise("Charles Dickens", "Oliver Twist", "novel")
print(book_author(book)) # Charles Dickens
print(book_title(book)) # Oliver Twist
print(book_genre(book)) # novel
まだ唖然として、私はTitoを見て、もう一つprint文を書きました。
print(book) # ((15, 27), [67, 104, 97, 114, 108, 101, 115, 32, 68, 105, 99, 107, 101, 110, 115, 79, 108, 105, 118, 101, 114, 32, 84, 119, 105, 115, 116, 110, 111, 118, 101, 108])
「Tito、これは一体何なんだ?この本をプリントすると、数字しか見えないんだけど!」
「それは、本をUnicodeコードポイントのシーケンスとして表現するのが理にかなっていると思ったからです。それらは個々のフィールドの累積長と共に格納されています。しかし、補助関数book_xxxがユーザーが対話すべきインターフェースを表すものなので、心配する必要はありません。」
そして、本の内部表現は少し奇妙に見えましたが、Titoは一つのことについては正しかったです。
彼の補助関数book_xxxが機能していたので、私が以前に書いたfind_xxx関数も機能しました。
catalog = [
book_initialise("Charles Dickens", "A Christmas Carol", "novella"),
book_initialise("Charles Dickens", "Oliver Twist", "novel"),
book_initialise("Charles Dickens", "Great Expectations", "novel"),
]
for book in find_by_title(catalog, "ol"):
print(book_title(book)) # A Christmas Carol
# Oliver Twist
本の作業が終わると、著者について作業を始めました。
著者名が単なる文字列であるだけでは不十分であることがわかりました。
著者は3つの情報に依存します。
名
姓
誕生年
それに加えて、著者に関連するいくつかの関数があります。
author_initialise(first_name, last_name, birth_year): 提供されたデータに基づいて著者の表現を構築します
author_name(author): 著者のフルネームを返します
author_birth_year(author): 著者の誕生年を返します
author_age(author): 現在の著者の年齢を返します
Unicodeコードポイントと長さを持つ奇妙な本の表現は少しこじつけなので、著者にはより自然なものを選択できます。
以下に示すauthor_initialise関数のように、3要素のタプルを使用して著者を表現できます。
def author_initialise(first_name, last_name, birth_year):
return (first_name, last_name, birth_year)
author_initialise関数を引数でタプルを作成するだけの関数と考えると、その関数は非常に無用に見えます。
しかし、author_initialise関数の力はそこにありません。
この関数は3つの引数を受け取り、著者を返すため有用です。
著者がどのように表現されるかは実装の詳細です。
この時点で、それは明らかです