Python – OOP Part 1. オブジェクト指向プログラミング(OOP)とは何か?なぜ使うのか?

読了 11分

今回の講座では、オブジェクト指向プログラミング(Object-Oriented Programming, OOP)について学んでいきます。

OOPは内容が少し多いため、本講座を含めて次のように7つの講座に分けて進めていきます。

  1. オブジェクト指向プログラミング(OOP)とは何か?なぜ使うのか?
  2. クラスとインスタンス(Class and Instance)
  3. クラス変数(Class Variable)
  4. クラスメソッドとスタティックメソッド(Class Method and Static Method)
  5. 継承とサブクラス(Inheritance and Subclass)
  6. マジックメソッド(Magic Method)
  7. プロパティデコレータ(Property Decorator) – Getters, Setters, Deleters

オブジェクト指向プログラムとは? #

Wikipediaによる OOP の定義は次のとおりです。

オブジェクト指向プログラミング(英語: Object-Oriented Programming, OOP)はコンピュータプログラミングのパラダイムの一つです。オブジェクト指向プログラミングは、コンピュータプログラムを命令の羅列として見る視点から離れ、複数の独立した単位、すなわち「オブジェクト」の集まりとして捉えようとするものです。それぞれのオブジェクトはメッセージをやり取りし、データを処理することができます。

オブジェクト指向プログラミングはプログラムを柔軟かつ変更しやすくするため、大規模ソフトウェア開発で多く使われます。また、プログラミングをより学びやすくし、ソフトウェアの開発と保守を容易にし、より直感的なコード分析を可能にするという長所があります。しかし、行き過ぎたプログラムのオブジェクト化傾向は、現実世界の姿をそのまま反映できないと批判されることもあります。

プログラムは特定の目的を持ってデータを処理するために作られます。しかし、複雑なデータを多数の関数だけで処理していると、さまざまなエラーやバグが発生する問題点が出てきます。OOPはこのような問題を解決し、複雑なデータをより簡単に処理できるよう手助けしてくれます。それでは、OOP とは何でしょうか?OOP とは、クラスという名前のブループリントを利用して新しいデータ型を作り、データと関数(クラスの中ではメソッドと呼びます)の論理的なグループを作って使うものだと考えていただければよいです。

注意点!

一般的にプログラムを作るときに常に念頭に置くべき、とても重要なポイントが2つあります。

  • 同じコードを繰り返しません。DRY (Don’t Repeat Yourself)
  • コードは常に変わり得るということを覚えておく。

もしコーディングをするときにコピー&ペーストを多用しているのなら、そのコードには重複しているコードが多いということであり、こうした重複部分は多くの問題を引き起こすため、必ず最小化する必要があります。重複するコードはコーディング時間を増やすだけでなく、厄介なバグを生み出し、コードの変更時に多くの箇所を修正しなければならないという問題を発生させます。そのため、一般的にはこのような重複したコードを減らすために関数を使います。関数を使えば、一度定義した関数を必要なところで呼び出すだけでよく、コードを修正するときには一箇所だけ修正すればよくなります。

OOPもまた関数と同じように、繰り返されるコードをなくしてコーディング時間を削減し、コードの管理をより簡単にしてくれます。ただし、一度使って捨てる不要なクラスを作ることもまた避けるべきだという点を忘れてはいけません。

オブジェクト指向プログラムはなぜ使うのか? #

今回の講座ではまず、どんな場合にクラスを使うべきかについて確認し、次の講座からはクラスの機能と使い方について学んでいきます。

私たちが好きなゲームのキャラクターを作る例を見てみましょう。名前、エネルギー、ダメージ、インベントリを持つ簡単なキャラクターを作ってみます。もしクラスを使わなければ、次のようにキャラクターを定義することができるでしょう。

お好きなディレクトリに「oop.py」という名前の Python ファイルを作成した後、次のコードを保存してください。

oop.py
hero_name = 'アイアンマン'
hero_health = 100
hero_damage = 200
hero_inventory = [
{'gold': 500},
{'weapon': 'レーザー'}
]

「アイアンマン」という名前のキャラクターを作ってみました。ところで、ゲームにキャラクターが一人だけだとつまらないですよね?ヒーローとモンスターのキャラクターをもっと追加します。

oop.py
# ヒーロー 1
hero_1_name = 'アイアンマン'
hero_1_health = 100
hero_1_damage = 200
hero_1_inventory = [
{'gold': 500},
{'weapon': 'レーザー'}
]

# ヒーロー 2
hero_2_name = 'デッドプール'
hero_2_health = 300
hero_2_damage = 30
hero_2_inventory = [
{'gold': 300},
{'weapon': '長剣'}
]

# ヒーロー 3
hero_3_name = 'ウルヴァリン'
hero_3_health = 200
hero_3_damage = 50
hero_3_inventory = [
{'gold': 350},
{'weapon': 'クロー'}
]

# モンスター 1
monster_1_name = 'ゴブリン'
monster_1_health = 90
monster_1_damage = 30
monster_1_inventory = [
{'gold': 50},
{'weapon': '槍'}
]

# モンスター 2
monster_2_name = 'ドラゴン'
monster_2_health = 200
monster_2_damage = 80
monster_2_inventory = [
{'gold': 200},
{'weapon': '火炎'}
]

# モンスター 3
monster_3_name = 'ヴァンパイア'
monster_3_health = 80
monster_3_damage = 120
monster_3_inventory = [
{'gold': 1000},
{'weapon': '催眠術'}
]

誰かが、プログラミングの基本はコピー&ペーストだと言っていましたが…一生懸命コピーして貼り付けました。 🥵 しかし上のコードは誰が見ても、よくできたコードではなさそうですね。リストを使って、もう少し洗練されたコードを作ってみます。

oop.py
hero_name = ['アイアンマン', 'デッドプール', 'ウルヴァリン']
hero_health = [100, 300, 200]
hero_damage = [200, 30, 50]
hero_inventory = [
{'gold': 500,'weapon': 'レーザー'},
{'gold': 300, 'weapon': '長剣'},
{'gold': 350, 'weapon': 'クロー'}
]

monster_name = ['ゴブリン', 'ドラゴン', 'ヴァンパイア']
monster_health = [90, 200, 80]
monster_damage = [30, 80, 120]
monster_inventory = [
{'gold': 50,'weapon': '槍'},
{'gold': 200, 'weapon': '火炎'},
{'gold': 1000, 'weapon': '催眠術'}
]

これで「アイアンマン」はインデックス0、「デッドプール」はインデックス1、「ウルヴァリン」はインデックス2を使ってキャラクターのデータにアクセスすることができます。しかし、上記のようなコードは簡単にバグを生み出します。その例を見てみましょうか?

oop.py
hero_name = ['アイアンマン', 'デッドプール', 'ウルヴァリン']
hero_health = [100, 300, 200]
hero_damage = [200, 30, 50]
hero_inventory = [
{'gold': 500, 'weapon': 'レーザー'},
{'gold': 300, 'weapon': '長剣'},
{'gold': 350, 'weapon': 'クロー'}
]

monster_name = ['ゴブリン', 'ドラゴン', 'ヴァンパイア']
monster_health = [90, 200, 80]
monster_damage = [30, 80, 120]
monster_inventory = [
{'gold': 50, 'weapon': '槍'},
{'gold': 200, 'weapon': '火炎'},
{'gold': 1000, 'weapon': '催眠術'}
]


# ヒーローが死んだ時に呼び出される関数
def hero_dies(hero_index):
del hero_name[hero_index]
del hero_health[hero_index]
del hero_damage[hero_index]
# <--- 開発者がうっかり del hero_inventory[hero_index] を抜かしたと仮定しましょう^^;;

hero_dies(0)

template = 'ヒーロー名: {}\nヒーロー体力: {}\nヒーローダメージ: {}\nヒーローインベントリ: {}'

print(template.format(hero_name[0],
hero_health[0],
hero_damage[0],
hero_inventory[0]))

ファイルを保存した後、実行してみます。

実行結果
ヒーロー名: デッドプール
ヒーロー体力: 300
ヒーローダメージ: 30
ヒーローインベントリ: {'gold': 500, 'weapon': 'レーザー'}

上のコードのように、ヒーローのエネルギーが0になって死んだときにヒーローをリストから削除する関数を追加しました。しかし、開発者がうっかりコード一行を入れていなかったとすると、「デッドプール」が死んだ「アイアンマン」のレーザーを使ってしまうという問題が発生します。

このような問題は、下のコードのように各ヒーローのデータをPython辞書に入れてリストでまとめることで解決できます。

oop.py
heroes = [
{'name': 'アイアンマン', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'レーザー'}},
{'name': 'デッドプール', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': '長剣'}},
{'name': 'ウルヴァリン', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'クロー'}}
]

monsters = [
{'name': 'ゴブリン', 'health': 90, 'damage': 30, 'inventory': {'gold': 50, 'weapon': '槍'}},
{'name': 'ドラゴン', 'health': 200, 'damage': 80, 'inventory': {'gold': 200, 'weapon': '火炎'}},
{'name': 'ヴァンパイア', 'health': 80, 'damage': 120, 'inventory': {'gold': 1000, 'weapon': '催眠術'}}
]

print('# アイアンマン 削除前')
print(heroes)
del heroes[0]
print('\n# アイアンマン 削除後')
print(heroes)
実行結果
# アイアンマン 削除前
[{'name': 'アイアンマン', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'レーザー'}}, {'name': 'デッドプール', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': '長剣'}}, {'name': 'ウルヴァリン', 'health': 200, 'd
amage': 50, 'inventory': {'gold': 350, 'weapon': 'クロー'}}]

# アイアンマン 削除後
[{'name': 'デッドプール', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': '長剣'}}, {'name': 'ウルヴァリン', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'クロー'}}]

この方法によってデータハンドリングは簡単になりましたが、もしヒーローのインベントリにいくつかのアイテムを持ったバッグがあるとした場合、辞書とリストはネストされ、コードはさらに複雑になっていきます。もう一つの問題点としては、同じコードがたくさん繰り返されていることが見て取れます。このような場合がOOPを使うべき良い例です。OOPを使って繰り返されるコードをなくし、辞書やリストではサポートされない継承のようなクラスの機能を使うことができます。

上で、クラスは新しいデータ型を作るブループリントだと述べました。ブループリントとは日本語で「青写真」と呼ばれ、建築物や自動車を設計した図面のことです。自動車のモデルをデザインし、設計したブループリントを利用して、同じモデルの自動車を望むだけ生産することができるわけです。プログラミングのクラスも同じ概念です。

上のヒーローとモンスターもまた同じ種類のデータを持っているため、次のコードのようにクラスを利用して論理的な集合としてまとめることができます。

oop.py
# classの定義
class Character:
def __init__(self, name, health, damage, inventory):
self.name = name
self.health = health
self.damage = damage
self.inventory = inventory

    def __repr__(self):
        return self.name


# Characterクラスのオブジェクト生成
heroes = []
heroes.append(Character('アイアンマン', 100, 200, {'gold': 500, 'weapon': 'レーザー'}))
heroes.append(Character('デッドプール', 300, 30, {'gold': 300, 'weapon': '長剣'}))
heroes.append(Character('ウルヴァリン', 200, 50, {'gold': 350, 'weapon': 'クロー'}))

monsters = []
monsters.append(Character('ゴブリン', 90, 30, {'gold': 50, 'weapon': '槍'}))
monsters.append(Character('ドラゴン', 200, 80, {'gold': 200, 'weapon': '火炎'}))
monsters.append(Character('ヴァンパイア', 80, 120, {'gold': 1000, 'weapon': '催眠術'}))

template = '{}'
print('# ヒーローリスト確認')
print(heroes)

print('\n# ヒーローデータ確認')
for hero in heroes:
print(hero.__dict__)

print('\n# モンスターリスト確認')
print(monsters)

print('\n# モンスターデータ確認')
for monster in monsters:
print(monster.__dict__)

del heroes[0]  # ヒーローリストからアイアンマンを削除

print('\n# ヒーローリスト再確認')
print(heroes)

print('# ヒーローデータ再確認')
for hero in heroes:
print(hero.__dict__)
実行結果
# ヒーローリスト確認
[アイアンマン, デッドプール, ウルヴァリン]
# ヒーローデータ確認
{'name': 'アイアンマン', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'レーザー'}}
{'name': 'デッドプール', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': '長剣'}}
{'name': 'ウルヴァリン', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'クロー'}}
# モンスターリスト確認
[ゴブリン, ドラゴン, ヴァンパイア]
# モンスターデータ確認
{'name': 'ゴブリン', 'health': 90, 'damage': 30, 'inventory': {'gold': 50, 'weapon': '槍'}}
{'name': 'ドラゴン', 'health': 200, 'damage': 80, 'inventory': {'gold': 200, 'weapon': '火炎'}}
{'name': 'ヴァンパイア', 'health': 80, 'damage': 120, 'inventory': {'gold': 1000, 'weapon': '催眠術'}}
# ヒーローリスト再確認
[デッドプール, ウルヴァリン]
# ヒーローデータ再確認
{'name': 'デッドプール', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': '長剣'}}
{'name': 'ウルヴァリン', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'クロー'}}

ここまで Character というクラスを使って OOP を実装してみました。まだ OOP についてピンとこない方も心配しないでください。この OOP シリーズをすべて読めば、完璧に理解できるようになるはずです。 😄

オブジェクト指向プログラムについての最初の紹介はここまでにし、次の講座では具体的なクラスの使用方法について学んでいきます。

X