【オブジェクト指向】5分でわかるポリモーフィズム

プログラミングをする犬 プログラミング

この記事では、オブジェクト指向における「ポリモーフィズム」についてわかりやすく解説します。

説明にはプログラミング言語の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_areacalculate_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」の追加が簡単になりました。

このようにポリモーフィズムを使うことで、新機能の追加や保守にかかるコストを軽減できるのです。

まとめ

この記事ではオブジェクト指向におけるポリモーフィズムを解説しました。

ポリモーフィズムとは、同じメソッドの動作が、呼び出し元のクラスによって変わる性質のことです。

ポリモーフィズムをうまく活用することでコードの再利用性や柔軟性、保守性を高めることができます。

ポリモーフィズムは、慣れるまでは難しい概念ですが、使いこなせるようになれば設計の幅が大きく広がる強力なツールです。

ぜひ使いながら覚えてポリモーフィズムをマスターしましょう。

タイトルとURLをコピーしました