この記事では、オブジェクト指向における「ポリモーフィズム」についてわかりやすく解説します。
説明にはプログラミング言語のPythonを使用しますが、他の言語でも考え方は同じです。
他の言語を使用している方も安心して読んでください。
ポリモーフィズムとは?
ポリモーフィズム(Polymorphism)とは「同じ親クラスを継承した子クラスののメソッドが、オブジェクトによって異なる動作をする性質」のことです。
ふつう、共通の親クラスを継承した子クラスたちは、同じ名前のメソッドを持っています。
たとえば、以下のように「Carクラス」を継承した「ElectricCarクラス」や「GasolineCarクラス」は親クラスである「Carクラス」と同様に「runメソッド」と「chargeメソッド」を持っています。
class Car:
def run(self):
# 車を走らせる
def charge(self):
# 燃料を補給する
class ElectricCar(Car): # Carクラスを継承
def run(self):
# 車を走らせる
def charge(self):
# 燃料を補給する
class GasolineCar(Car): # Carクラスを継承
def run(self):
# 車を走らせる
def charge(self):
# 燃料を補給する
この例で、電気自動車(ElectricCar)とガソリン車(GasolineCar)は同じ「自動車(Car)」です。
しかし、同じ自動車でも燃料供給(charge)の方法は異なります。
電気自動車ではバッテリーを充電しますが、ガソリン車では給油します。
chargeメソッドを持っているのは同じですが、その実装内容は異なります。
class ElectricCar(Car):
def run(self):
pass
def charge(self):
# バッテリーを充電する
class GasolineCar(Car):
def run(self):
pass
def charge(self):
# ガソリンを給油する
このように同じクラスを継承した子クラスのメソッドの動作が、呼び出し元のオブジェクトによって変わる性質を「ポリモーフィズム」といいます。
日本語では「多態性(たたいせい)」と呼ぶこともあります。同じメソッドが、呼び出し元のクラス次第で「多様なふるまい(態)」をするからです。
どんなときにポリモーフィズムを使うのか?
ポリモーフィズムを使えば異なる機能を持ったメソッドの呼び出しを、共通のインターフェースで利用できます。
これによってコードの再利用性が高まります。
また、新しいクラスを追加する際に、既存のコードを変更する必要がないため、実装の柔軟性が高まります。
たとえば、以下のコードを考えてみましょう。
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius**2
def calculate_rectangle_area(rectangle):
print(rectangle.area())
def calculate_circle_area(circle):
print(circle.area())
rectangle = Rectangle(4, 5)
circle = Circle(3)
calculate_rectangle_area(rectangle) # Output: 20
calculate_circle_area(circle) # Output: 28.27431
RectangleクラスとCircleクラスがあり、それぞれが面積計算をするarea
メソッドを持っています。Rectangleは長方形で、Circleは円ですからarea
メソッドの実装内容はそれぞれ異なります。
このコードでは、それぞれのクラスのインスタンスを生成した後に、calculate_rectangle_area
メソッドとcalculate_circle_area
メソッドを呼び出し、それぞれのインスタンスを引数に渡して計算結果を出力しています。
RectangleクラスとCircleクラスは別物なので、calculate_rectangle_area
メソッドとcalculate_circle_area
メソッドの2つのメソッドを作成し、calculate_rectangle_area
の引数にはRectangleオブジェクトを、calculate_circle_area
メソッドの引数にはCircleオブジェクトを渡すようになっています。
ここで新たにTriangleクラスを追加して三角形の面積計算をさせるとしましょう。
このとき、他の2つの形状と同様にcalculate_triangle_area
メソッドを作成しなければなりません。
なぜなら、RectangleとCircleとTriangleが完全に別の物として実装されているからです。
このように複数のクラスが似たようなメソッドを持っているにも関わらず、共通化されていないと実装に手間がかかります。
ここで、RectangleクラスとCircleクラスはどちらも図形を扱うクラスです。
これらを図形クラス(Shapeクラス)として抽象化することを考えてみましょう。
Shapeクラスを新たに作成します。さらに、Shapeクラスを親クラスとしてRectangleクラスとCircleクラスに継承させます。
RectangleクラスとCircleクラスには、Shapeクラスのarea
メソッドを上書きする形で実装します。
ただし、area
メソッドの呼び出し側は、area
メソッドを持つオブジェクトが何であれ、同じように呼び出すことができます。
このようにしてポリモーフィズムが実現します。
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius**2
def calculate_area(shape): # Shapeを継承するオブジェクトなら何でも同じように面積を計算できる
print(shape.area())
rectangle = Rectangle(4, 5)
circle = Circle(3)
calculate_area(rectangle) # Output: 20
calculate_area(circle) # Output: 28.27431
これまではcalculate_rectangle_area
やcalculate_circle_area
のようにオブジェクトごとに面積計算のメソッドを作成していました。
しかし、RectangleクラスとCircleクラスがともにShapeクラスを継承したことで、calculate_area
メソッドという1つのメソッドで、どんなShapeのクラスでも面積を計算できるようになりました。
このようにポリモーフィズムを使えばオブジェクトを使う側の実装コストを軽減させることができます。
練習問題
ここでポリモーフィズムを使う練習をしましょう。
現在、あなたはコンビニのレジの支払いシステムを作っているとします。
レジでの支払いには現金、クレジットカードが使えます。
現在のコードは以下のように実装されています。
class CashPaymentService():
def __init__(self):
pass
def pay(self):
# 現金を使う場合の支払い処理
pass
class CreditCardPaymentService():
def __init__(self):
pass
def pay(self):
# クレジットカードを使う場合の支払い処理
pass
def pay_with_cash(cashPaymentService):
cashPaymentService.pay()
def pay_with_credit_card(creditCardPaymentService):
creditCardPaymentService.pay()
if user_input == "cash":
cash_payment_service = CashPaymentService()
pay_with_cash(cash_payment_service)
if user_input == "credit_card":
credit_card_payment_service = CreditCardPaymentService()
pay_with_credit_card(credit_card_payment_service)
ここで支払い方法として「バーコードPay」という決済方法を追加することになりました。
「バーコードPay」を追加するにあたって、ポリモーフィズムを使ってコードを整理したいです。
上記のコードをポリモーフィズムを使って書き換えた後、「バーコードPay」の支払いを処理する「BarcodePayPaymentServiceクラス」を実装し、user_inputが「barcode_pay」のときに支払いが「バーコードPay」を使用して処理されるようなコードを書いてください。
練習問題の解答例
まずは与えられたコードをポリモーフィズムを使って書き換えます。
ここでは、それぞれの支払いサービスを「PaymentService」として抽象化することを考えます。
それぞれの支払いサービスはpay
メソッドを持っています。
よって、以下のようにPaymentServiceクラスを定義して、それぞれの支払いサービスに継承させます。
class PaymentService:
def pay(self):
pass
class CashPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# 現金を使う場合の支払い処理
pass
class CreditCardPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# クレジットカードを使う場合の支払い処理
pass
これによって、それぞれよ支払いサービスがPaymentSerivceの子クラスになりました。
PaymentSerivceを継承しているのでpay
メソッドを持っていることが保証されます。
PaymentServiceオブジェクトを引数にとり、そのオブジェクトのpay
メソッドを呼び出す処理を共通メソッド(process_payment
メソッド)として以下のように定義します。
def process_payment(payment_service):
payment_service.pay()
user_inputによって支払いサービスを分けますが、支払い処理自体はどの支払いサービスであってもオブジェクトをprocess_payment
メソッドの引数に渡すだけです。
if user_input == "cash":
payment_service = CashPaymentService()
if user_input == "credit_card":
payment_service = CreditCardPaymentService()
process_payment(payment_service)
これでポリモーフィズムを使ってコードを書き直すことができました。
現時点で、コードは以下のようになっています。
class PaymentService:
def pay(self):
pass
class CashPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# 現金を使う場合の支払い処理
pass
class CreditCardPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# クレジットカードを使う場合の支払い処理
pass
def process_payment(payment_service):
payment_service.pay()
if user_input == "cash":
payment_service = CashPaymentService()
if user_input == "credit_card":
payment_service = CreditCardPaymentService()
process_payment(payment_service)
最後に、新たな支払いサービスとして「BarcodePayPaymentServiceクラス」を追加します。
他の支払いサービスと同じようにPaymentServiceクラスを継承して実装し、user_inputが「barcode_pay」のときにインスタンスを生成するだけです。
完成形は以下になります。
class PaymentService:
def pay(self):
pass
class CashPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# 現金を使う場合の支払い処理
pass
class CreditCardPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# クレジットカードを使う場合の支払い処理
pass
class BarcodePayPaymentService(PaymentService):
def __init__(self):
pass
def pay(self):
# バーコードPayを使う場合の支払い処理
pass
def process_payment(payment_service):
payment_service.pay()
if user_input == "cash":
payment_service = CashPaymentService()
elif user_input == "credit_card":
payment_service = CreditCardPaymentService()
elif user_input == "barcode_pay":
payment_service = BarcodePayPaymentService()
process_payment(payment_service)
ポリモーフィズムを使ってコードを整理したことによって、新しい支払いサービスである「バーコードPay」の追加が簡単になりました。
このようにポリモーフィズムを使うことで、新機能の追加や保守にかかるコストを軽減できるのです。
まとめ
この記事ではオブジェクト指向におけるポリモーフィズムを解説しました。
ポリモーフィズムとは、同じメソッドの動作が、呼び出し元のクラスによって変わる性質のことです。
ポリモーフィズムをうまく活用することでコードの再利用性や柔軟性、保守性を高めることができます。
ポリモーフィズムは、慣れるまでは難しい概念ですが、使いこなせるようになれば設計の幅が大きく広がる強力なツールです。
ぜひ使いながら覚えてポリモーフィズムをマスターしましょう。