【デザインパターン with Python】1-6. 抽象Classとは何か

記事タイトルとURLをコピーする

はじめに

こんにちは。孔子の80代目子孫兼アプリケーションサービス部の孔です。今回は抽象クラス(Abstract Class)に関してみてみましょう。

抽象とは?

クラスは「データと処理の集まり」であることはわかったので、抽象というのがどのような意味かが分かればよさそうですね。「抽象」という言葉は、簡単にいうと「実装をしないで宣言のみした」何かを指すときに使う言葉になります。まずは単位を小さくするために、抽象メソッドの例を見てみましょう。前回作成したコードを再掲します。

# 親クラス
class Class:
    def attack(self):
        return self.attack_point
 
    # デフォルトでは、このクラスを継承するクラスは剣を装備できる
    def equipment(self, weapon_type):
        if weapon_type != "sword":
            print("その武器は使用できません")
            return None
 
# 戦士クラス
class Warrior(Class):
    def __init__(self):
        self.attack_point = 20
        self.speed = 30
        self.dexterity = 10
        self.weapon = ""
 
# 野蛮人クラス
class Barbarian(Class):
    def __init__(self):
        self.attack_point = 50
        self.speed = 10
        self.dexterity = 0
        self.weapon = ""
 
    def roar(self):
        self.attack_point += 50
        self.dexterity -= 50
 
# 弓使いクラス
class Archer(Class):
    def __init__(self):
        self.attack_point = 10
        self.speed = 40
        self.dexterity = 50
        self.weapon = ""
 
    # 継承元のメソッド名と同様のメソッド名を定義することで、オーバーライドすることができます!
    def equipment(self, weapon_type):
        if weapon_type != "bow":
            print("その武器は使用できません")
            return None
 
    def power_of_nature(self):
        self.attack_point += 10
        self.speed += 10
        self.dexterity += 10

前回のブログ1-5. Classを使ってみよう: 継承とオーバーライドで作成したコードになります。その際に、「attackメソッドとequipmentメソッドは処理が共通しているから親クラスに実装しましょう」と言いましたが、よく考えてみるとこれは少し不便です。

例えば、「equipment」がたままた戦士、野蛮人クラスが同じ処理をしていて、弓使いだけが違った処理をしていますが、野蛮人クラスが斧を使うように処理が変わったとしたらどうでしょう?装着しようとした武器が戦士は剣、野蛮人は斧、弓使いは弓かどうかを判定するようになり、これだとすべてのクラスが違った処理をしていますね。このような時に登場するのが「宣言のみやって実装を子クラスに任せる抽象メソッド」になります。正しい書き方は後ほど紹介しますが、いったん以下のように実装してみましょう。

# 親クラス
class Class:
    def attack(self):
        return self.attack_point
 
    # 抽象メソッド。関数名の宣言のみやって、実装は継承先の子クラスでそれぞれ実施する。
    def equipment(self, weapon_type):
        pass
 
# 戦士クラス
class Warrior(Class):
    def __init__(self):
        self.attack_point = 20
        self.speed = 30
        self.dexterity = 10
        self.weapon = ""
 
    # 抽象メソッドを実装する
    def equipment(self, weapon_type):
        if weapon_type != "sword":
            print("その武器は使用できません")
            return None
 
# 野蛮人クラス
class Barbarian(Class):
    def __init__(self):
        self.attack_point = 50
        self.speed = 10
        self.dexterity = 0
        self.weapon = ""
 
    def roar(self):
        self.attack_point += 50
        self.dexterity -= 50
 
    # 抽象メソッドを実装する
    def equipment(self, weapon_type):
        if weapon_type != "axe":
            print("その武器は使用できません")
            return None
 
# 弓使いクラス
class Archer(Class):
    def __init__(self):
        self.attack_point = 10
        self.speed = 40
        self.dexterity = 50
        self.weapon = ""
 
    # 抽象メソッドを実装する
    def equipment(self, weapon_type):
        if weapon_type != "bow":
            print("その武器は使用できません")
            return None
 
    def power_of_nature(self):
        self.attack_point += 10
        self.speed += 10
        self.dexterity += 10

これで、「equipment」は親クラスで抽象メソッドとして宣言され、実装は子クラスでされるようになりました!抽象メソッドを使う理由はいくつかありますが、最も大きなメリットは「ポリモーフィズム」です。

ポリモーフィズム

ポリモーフィズムは「異なる型に同じメソッド名を使って違った処理を行うこと」を指します。よく例として挙げられるのは動物の鳴き声の例になります。以下の例を見てください。

class Animal:
    # 抽象メソッド
    def cry(self):
        pass
 
class Dog(Animal):
    def cry(self):
        print('わん!')
 
class Cat(Animal):
    def cry(self):
        print('にゃー')
 
class Cow(Animal):
    def cry(self):
        print('もー')
 
hachi = Dog()
tama = Cat()
ushi = Cow()
hachi.cry()  # わん!
tama.cry()  # にゃー
ushi.cry()  # もー

それぞれ異なるクラスに対して、同じメソッド名を使って異なる処理をしていますね。先ほどRPGゲームの例でいうと、すべてのクラスは同じく「equipment」というメソッドを呼び出しても、それぞれ異なる処理をするのもポリモーフィズムの例になります。

このような書き方は協業をするときに特に効果を最大限に発揮します。開発をする際にメソッドの命名方式は、特定のクラスを継承して何か開発をしたいときに、抽象メソッドの命名に従うと、チーム全体の命名規則が守られたり、パッと見てどのような処理をするものかの概要がわかるようになります。コードの共有が最も大事な協業の現場において、とても大きなメリットになります。

抽象クラスとは何か

それでは、抽象の意味も分かったところで、本題に戻って抽象クラスとは何かについてみてみましょう。抽象クラスとは「抽象メソッドを含んだクラス」になります。抽象クラスを1つでも含んでいたら、それは抽象クラスになります。そのため、抽象クラスは「そのクラスからインスタンスを作ることはできず、必ず継承して抽象メソッドを実装してから使用しなければならない」という特徴を持ちます。

これは先ほどの鳴き声の実装を例を見ると、抽象メソッド「cry」が実装されていない「Animal」クラスからインスタンスを作成することはできず、「cry」メソッドを実装した子クラスである「Dog」クラスなどがインスタンス化可能である、といったことになります。先ほどは抽象メソッドを実装する際に「pass」を使って実装をしないことを表しましたが、Pythonでは抽象クラスおよび抽象メソッドを実装するための標準ライブラリ「ABC」を提供しています(Abstract Base Classの略語です)

それでは、先ほど書いた動物の鳴き声の実装をPythonの標準ライブラリを使って再度実装してみましょう。

from abc import ABC, abstractmethod
 
# ABC型を継承したクラスは抽象クラスになります
class Animal(ABC):
    # @abstractmethodデコレーターを付けると、抽象メソッドになります
    @abstractmethod
    def cry(self):
        pass
 
class Dog(Animal):
    def cry(self):
        print('わん!')
 
class Cat(Animal):
    def cry(self):
        print('にゃー')
 
class Cow(Animal):
    def cry(self):
        print('もー')
 
hachi = Dog()
tama = Cat()
ushi = Cow()
hachi.cry()  # わん!
tama.cry()  # にゃー
ushi.cry()  # もー

ABCライブラリのドキュメントは以下となりますので、もしご興味あればご参照ください。

abc --- 抽象基底クラス — Python 3.11.0b5 ドキュメント

最後に

今回は「抽象」の意味および抽象クラス、抽象メソッドをPythonでどのように実装するかを見てみました。次回はクラス間の関係を表す用語およびその関係を表す図であるUMLを見てみます。

シリーズ一覧

孔 允培 (執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス課

孔子の80代目子孫