🧚‍♀️

Pythonでデザインパターンを学ぶ(Adapterパターン)

2022/11/13に公開

こんにちは。深緑です。
チーム内で改めてデザインパターンを学び始めました。
記録のために記事に残しておこうと思います。

はじめに

GoFのデザインパターンを一つずつ学んでいきます。
今回はAdapterパターンです。
Wikipedia - Adapter パターン
言語はPythonを使用します。

サンプルコードのシチュエーション

学校において、現在生徒を特定するコードを和暦+学部+学科+数字4桁で管理しています。
令和5年度から西暦+学部+学科+数字4桁で管理するとします。

年度 コードのフォーマット
令和4年度まで 和暦+学部+学科+数字4桁 04TEIT0001
令和5年度以降 西暦+学部+学科+数字4桁 2023TEIT0001

サンプルコードにおいては、既存クラスには最後の数字を和暦+学部+学科ごとに自動採番するロジックがあり、
これを作り直すとなるとそれなりに手間がかかるとします。

サンプルコード

Adapterパターンには、「継承を利用したAdapter」と「委譲を利用したAdapter」があります。
今回は、「継承を利用したAdapter」で実装してみます。

Adapterパターン適用前


class Student:
    """
    Adapterパターン 生徒クラス
    """

    year: int = 0
    department: str
    course: str
    num: int = 0

    def __init__(self, year: int, department: str, course: str):
        """コンストラクタ
        Args:
            year (int): 年度
            department (str): 学部
            course (str): 学科
        """
        self.year = year
        self.department = department
        self.course = course

    def code(self) -> str:
        """生徒コードを返す
        Returns:
            str: 生徒コード
        """

        # まだ連番が振られてない場合、年度・学部・学科ごとの連番を自動採番する
        # このソースでは長くなるので省略

        return str(self.year).zfill(2) \
            + self.department \
            + self.course \
            + str(self.num).zfill(4)

Adapterパターン適用後


from abc import ABCMeta, abstractmethod


class SeirekiInterface(metaclass=ABCMeta):
    """
    Adapterパターン 西暦インターフェース
    """

    @abstractmethod
    def seireki_cd(self) -> str:
        """生徒コード(西暦版)を取得する
        Returns:
            str: 生徒コード(西暦版)
        """
	

from student import Student
from seireki_interface import SeirekiInterface


class SeirekiStudent(Student, SeirekiInterface):
    """
    Adapterパターン 生徒(西暦Ver)クラス
    """

    def seireki_cd(self) -> str:
        """生徒コード(西暦版)を取得する
        Returns:
            str: 生徒コード(西暦版)
        """
        code: str = self.code()

        # コードが12桁以上なら西暦+4桁の番号になってるのでそのまま返却
        if (len(code) >= 12):
            return code

        year: int = int(code[0:2])
        # 令和元年〜10年の場合
        # 令和5年以降は年そのものに西暦が入力されていく予定
        if (year <= 10):
            year = year + 2018

        # 平成元年〜31年の場合
        if (year <= 31):
            year = year + 1988

        return str(year).zfill(2) \
            + self.department \
            + self.course \
            + str(self.num).zfill(4)
        

解説

この例では、Studentクラスが和暦ベースでコードを生成しています。
学校の業務全体で各種コードが西暦ベースのコードに変わったら、
今のStudentクラスでは対応できないので新しいルールのコードを返すメソッドを追加しています。

新しいメソッドは、あえて既存の関数の戻り値を加工して新しいルールに合わせています。
既存のStudentクラスは自動採番などあり、それなりに変えるのは面倒だと思ってください。

インターフェースは、仮に西暦ベースのコードのルールをStudentクラス以外にも適用する場合、
西暦ベースのコードを返す関数を作るのを強要するのに役立ちます。

このように、インターフェースが変わってしまった場合に、既存クラスに修正を加えることなくインターフェースを合わせるための実装方法がAdapterパターンです。
実務ではAPIのバージョン変更などで使うことがあります。

補足

実務では新しい関数を追加すると言うよりも、
既存の関数をoldにしてから、新たなルールの関数を同名で追加し直す方が多いかもしれませんね。

その他、誤りや解釈違いがあれば随時修正します。

Discussion