オブジェクト指向プログラミングとは?基本を解説
オブジェクト指向プログラミング(OOP)は、データとそれに関連する処理をひとまとめにした「オブジェクト」を中心にプログラムを組み立てる考え方です。Python、Java、JavaScriptなど多くの言語がオブジェクト指向をサポートしています。この記事では、その基本概念をわかりやすく解説します。
オブジェクト指向とは何か
オブジェクト指向は「現実世界のモノや概念をプログラムで表現する」ための考え方です。プログラムを「データ」と「処理」の集まりではなく、「オブジェクト(もの)」同士のやり取りとして設計します。
身近な例で考える
たとえば「自動車」を考えてみましょう。自動車には次のような特徴があります。
- 属性(データ): 色、メーカー、現在の速度、燃料の残量
- 操作(処理): 加速する、ブレーキをかける、ハンドルを切る
オブジェクト指向では、この「属性」と「操作」をひとまとめにして1つのオブジェクトとして扱います。
なぜオブジェクト指向が必要なのか
プログラムが小さいうちは、データと処理を別々に管理しても問題ありません。しかし、プログラムが大きくなると「どのデータにどの処理が関係するのか」が複雑になり、バグが生まれやすくなります。
オブジェクト指向を使うと、関連するデータと処理がまとまるため、コードの見通しが良くなり、修正や拡張がしやすくなります。
クラスとインスタンス
オブジェクト指向の基本は「クラス」と「インスタンス」です。クラスは設計図、インスタンスは設計図から作った実物と考えるとわかりやすいでしょう。
クラスの定義
class Dog:
def __init__(self, name, breed):
self.name = name # 名前
self.breed = breed # 犬種
def bark(self):
print(f"{self.name}がワンワンと吠えました")
def introduce(self):
print(f"名前: {self.name}、犬種: {self.breed}")
__init__ はインスタンスを作るときに自動的に呼ばれるメソッドで、「コンストラクタ」と呼ばれます。self はインスタンス自身を指す特別な引数です。
インスタンスの作成と利用
# インスタンスの作成
dog1 = Dog("ポチ", "柴犬")
dog2 = Dog("マル", "トイプードル")
# メソッドの呼び出し
dog1.bark() # ポチがワンワンと吠えました
dog2.introduce() # 名前: マル、犬種: トイプードル
# 属性へのアクセス
print(dog1.name) # ポチ
1つのクラスから複数のインスタンスを作れます。それぞれが独立したデータ(名前や犬種)を持っている点が重要です。
クラスを使わない場合との比較
クラスを使わずに辞書で管理する方法と比べてみましょう。
# 辞書で管理する場合
dog1 = {"name": "ポチ", "breed": "柴犬"}
dog2 = {"name": "マル", "breed": "トイプードル"}
def bark(dog):
print(f"{dog['name']}がワンワンと吠えました")
bark(dog1)
辞書でも動作しますが、「犬のデータ」と「犬に関する処理」が分離しているため、コードが大きくなると管理しにくくなります。クラスを使えば、データと処理が一体化し、構造が明確になります。
カプセル化
カプセル化は、オブジェクトの内部データを外部から直接操作できないようにする仕組みです。
なぜカプセル化が必要か
たとえば銀行口座を考えてみましょう。残高を外部から自由に書き換えられたら、不整合が起きてしまいます。
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # 先頭のアンダースコアは「外部から直接触らないでほしい」という慣習
def deposit(self, amount):
if amount <= 0:
print("入金額は正の数を指定してください")
return
self._balance += amount
print(f"{amount}円を入金しました。残高: {self._balance}円")
def withdraw(self, amount):
if amount > self._balance:
print("残高が不足しています")
return
self._balance -= amount
print(f"{amount}円を出金しました。残高: {self._balance}円")
def get_balance(self):
return self._balance
カプセル化の利点
account = BankAccount("田中太郎", 10000)
account.deposit(5000) # 5000円を入金しました。残高: 15000円
account.withdraw(3000) # 3000円を出金しました。残高: 12000円
account.withdraw(20000) # 残高が不足しています
残高の変更は必ず deposit() や withdraw() メソッドを通して行うため、不正な操作(マイナスの入金や残高超過の出金)を防げます。これがカプセル化の基本的な考え方です。
Pythonにおけるアクセス制御
Pythonでは厳密なアクセス制御はなく、慣習として以下のルールが使われます。
name: パブリック(外部からアクセスOK)_name: プロテクテッド(外部から触らないでほしいという慣習)__name: プライベート(名前修飾により外部からアクセスしにくくなる)
継承
継承は、既存のクラスをもとに新しいクラスを作る仕組みです。共通の機能を親クラスにまとめ、子クラスで固有の機能を追加できます。
継承の基本
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name}が鳴きました")
def sleep(self):
print(f"{self.name}が眠っています")
class Cat(Animal):
def purr(self):
print(f"{self.name}がゴロゴロ喉を鳴らしています")
class Dog(Animal):
def fetch(self):
print(f"{self.name}がボールを取ってきました")
Cat と Dog は Animal を継承しているため、speak() や sleep() を自分で定義しなくても使えます。
cat = Cat("ミケ")
cat.speak() # ミケが鳴きました
cat.purr() # ミケがゴロゴロ喉を鳴らしています
dog = Dog("ポチ")
dog.speak() # ポチが鳴きました
dog.fetch() # ポチがボールを取ってきました
メソッドのオーバーライド
親クラスのメソッドを子クラスで上書き(オーバーライド)できます。
class Cat(Animal):
def speak(self):
print(f"{self.name}がニャーと鳴きました")
class Dog(Animal):
def speak(self):
print(f"{self.name}がワンと吠えました")
cat = Cat("ミケ")
cat.speak() # ミケがニャーと鳴きました
dog = Dog("ポチ")
dog.speak() # ポチがワンと吠えました
継承を使いすぎない
継承は便利ですが、使いすぎるとクラスの関係が複雑になり、かえってコードが理解しにくくなります。「AはBの一種である」という関係が自然に成り立つ場合にのみ使うのがよいとされています。
多態性(ポリモーフィズム)
多態性は、異なるクラスのオブジェクトに対して同じ操作を適用できる性質です。先ほどのオーバーライドの例が、まさに多態性の一例です。
多態性の実践例
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
Circle と Rectangle はどちらも area() メソッドを持っていますが、計算方法はそれぞれ異なります。
shapes = [Circle(5), Rectangle(3, 4), Circle(10)]
for shape in shapes:
print(f"面積: {shape.area()}")
# 面積: 78.53975
# 面積: 12
# 面積: 314.159
多態性のメリット
呼び出す側のコードは shape.area() と書くだけで、円でも四角形でも正しく動作します。新しい図形(三角形など)を追加しても、呼び出し側を変更する必要がありません。
これが多態性の最大のメリットです。「具体的な型を知らなくても、共通の操作ができる」ため、プログラムの拡張性が高まります。
JavaScriptでのオブジェクト指向
JavaScriptでもクラスを使ったオブジェクト指向プログラミングができます。
JavaScriptのクラス構文
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
bark() {
console.log(`${this.name}がワンワンと吠えました`);
}
}
const dog = new Dog("ポチ", "柴犬");
dog.bark(); // ポチがワンワンと吠えました
Pythonの self が this、__init__ が constructor に対応します。基本的な考え方は同じです。
JavaScriptでの継承
Pythonと同様に extends キーワードで継承できます。メソッドのオーバーライドも同じ要領で行えます。
まとめ
この記事では、オブジェクト指向プログラミングの基本を解説しました。
- オブジェクト指向は「データと処理をまとめて管理する」考え方
- クラスは設計図、インスタンスは設計図から作った実物
- カプセル化でオブジェクトの内部データを保護する
- 継承で既存クラスの機能を引き継いだ新しいクラスを作れる
- 多態性により、異なるクラスに共通の操作を適用できる
オブジェクト指向は最初は抽象的に感じるかもしれませんが、実際にコードを書いて動かすことで理解が深まります。まずはこの記事のコード例をPythonで実行し、クラスやインスタンスの動きを確認してみてください。