この資料は、「Pythonサイコロゲームで学ぶ!基礎からオブジェクト指向プログラミング (3/3)」の演習(オブジェクト指向プログラミング)を進める上で重要な、OOPの基本的な概念について、サイコロゲームとは異なる題材を用いて解説します。
オブジェクト指向プログラミング(Object-Oriented Programming, OOP)は、プログラムを現実世界の「モノ(オブジェクト)」のように捉え、それらの「モノ」が持つ情報(データ)と、それらが行う操作(処理)をひとまとめにして扱う考え方です。
オブジェクトとは?
なぜOOPを使うのか?
クラス (Class):
Car
クラスは、「車」という種類のオブジェクトが共通して持つべき特徴(色、最高速度など)や操作(加速する、停止するなど)を定義します。class Car: # "Car"という名前のクラスを定義
# クラスの属性(クラス変数) - このクラスの全ての車に共通
maker_country = "Japan"
def __init__(self, color, max_speed): # コンストラクタ
# インスタンスの属性(インスタンス変数) - 各車ごとに異なる
self.color = color
self.max_speed = max_speed
self.current_speed = 0 # 初期速度は0
def accelerate(self, amount): # メソッド
if self.current_speed + amount <= self.max_speed:
self.current_speed += amount
else:
self.current_speed = self.max_speed
print(f"{self.color}の車が {self.current_speed} km/h に加速しました。")
def brake(self): # メソッド
self.current_speed = 0
print(f"{self.color}の車が停止しました。")
インスタンス (Instance):
my_car = Car("Red", 180)
のようにして作られた my_car
は、Car
クラスのインスタンス(赤い色で最高速度180km/hの具体的な車)です。# Carクラスのインスタンスを作成
car1 = Car("Blue", 200)
car2 = Car("Silver", 150)
car1.accelerate(50) # Blueの車が 50 km/h に加速しました。
car2.accelerate(30) # Silverの車が 30 km/h に加速しました。
print(f"car1の色: {car1.color}, car2のメーカー国: {car2.maker_country}")
# 出力: car1の色: Blue, car2のメーカー国: Japan
__init__
メソッド (コンストラクタ):
self
:
self.属性名
や self.メソッド名()
のようにして、インスタンスの属性や他のメソッドにアクセスします。インスタンス変数 (属性 Attribute):
car1
の self.color
は "Blue"、car2
の self.color
は "Silver")。メソッド (Method):
accelerate()
メソッド)。継承とは?
ElectricCar
クラスが Car
クラスを継承し、バッテリー容量という属性や充電メソッドを追加する。class Vehicle: # 親クラス
def __init__(self, brand):
self.brand = brand
self.is_moving = False
def start_engine(self):
print(f"{self.brand}のエンジンが始動しました。")
self.is_moving = True
def stop_engine(self):
print(f"{self.brand}のエンジンが停止しました。")
self.is_moving = False
class ElectricCar(Vehicle): # Vehicleクラスを継承する子クラス
def __init__(self, brand, battery_capacity):
super().__init__(brand) # 親クラスの__init__を呼び出す
self.battery_capacity = battery_capacity # 子クラス独自の属性
def charge(self):
print(f"{self.brand} (バッテリー: {self.battery_capacity}kWh) を充電中です。")
# 親クラスのメソッドをオーバーライド(上書き)
def start_engine(self):
print(f"{self.brand}の電気モーターが起動しました。静かです!")
self.is_moving = True
my_ev = ElectricCar("Tesla", 75)
my_ev.start_engine() # Teslaの電気モーターが起動しました。静かです!
my_ev.charge() # Tesla (バッテリー: 75kWh) を充電中です。
my_ev.stop_engine() # Teslaのエンジンが停止しました。 (親クラスのメソッドを利用)
super()
: 子クラスから親クラスのメソッドを呼び出す際に使います。特に __init__
で親クラスの初期化処理を呼び出すのによく使われます。
カプセル化 (Encapsulation):
Pythonにおける「プライベート」の慣習:
_
を1つ付ける(例: _balance
):__
を2つ付ける(例: __secret_code
):class BankAccount:
def __init__(self, owner_name, initial_deposit):
self.owner_name = owner_name
self._balance = initial_deposit # アンダースコア1つで「保護された」属性を示す
def deposit(self, amount):
if amount > 0:
self._balance += amount
print(f"{amount}円入金しました。残高: {self._balance}円")
else:
print("入金額は0より大きい値を指定してください。")
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
print(f"{amount}円出金しました。残高: {self._balance}円")
else:
print("出金額が不正か、残高が不足しています。")
def get_balance(self): # 残高確認用の公開メソッド
return self._balance
account = BankAccount("山田太郎", 50000)
# account._balance = -10000 # 直接変更すべきではない (できてしまうが非推奨)
account.deposit(10000)
print(f"{account.owner_name}さんの残高: {account.get_balance()}円")
@property
)@property
デコレータ:import math
class Circle:
def __init__(self, radius):
if radius <= 0:
raise ValueError("半径は正の値でなければなりません。")
self._radius = radius # 実際の半径は _radius に保存
@property # これを付けると radius メソッドが属性のように扱える
def radius(self):
"""円の半径(読み取り専用)"""
return self._radius
@property
def diameter(self):
"""円の直径(計算によって求められる読み取り専用プロパティ)"""
return self._radius * 2
@property
def area(self):
"""円の面積(計算によって求められる読み取り専用プロパティ)"""
return math.pi * (self._radius ** 2)
# 半径を変更するためのセッターメソッド (オプション)
# @radius.setter
# def radius(self, value):
# if value <= 0:
# raise ValueError("半径は正の値でなければなりません。")
# self._radius = value
my_circle = Circle(5)
print(f"半径: {my_circle.radius}") # my_circle.radius() ではなく属性のようにアクセス
print(f"直径: {my_circle.diameter}") # 計算結果が返る
print(f"面積: {my_circle.area:.2f}")
# my_circle.radius = 10 # @propertyだけでは書き込み不可 (セッターがない場合)
# my_circle.diameter = 20 # エラー!プロパティには直接代入できない
@property
を使うことで、メソッド呼び出しの ()
を書かずに済むため、コードがより自然に見えることがあります。また、内部の実装を変更しても、外部からのアクセス方法を変えずに済む利点もあります。
オブジェクト指向でプログラムを設計すると、特にプログラムが大きくなったり、機能が複雑になったりした場合に、以下のような多くの利点があります。
BankAccount
クラスの利息計算ロジックを変更しても、他のクラス(例えば Customer
クラス)には影響が出にくい。Vehicle
クラスを一度作れば、それを継承して Truck
クラス、Bus
クラスなど、様々な種類の乗り物クラスを効率的に作れます。Product
クラスを基に新しい商品カテゴリ(BookProduct
, ElectronicsProduct
)を追加するのが容易になる。これらの概念は、より複雑で大規模なプログラムを構築するための基礎となります。演習を通じて、オブジェクト指向の考え方に慣れていきましょう。