Python | OOP:インターフェース的設計

Python Python
スポンサーリンク
  1. 概要(「インターフェース的設計」は“共通の窓口”で差し替え可能にすること)
  2. 基本の考え方(「同じメソッド名で呼べる」ことが設計の核)
    1. インターフェースの要点(統一窓口で多態性を成立させる)
    2. Pythonでの実現手段(ABC・Protocol・ダックタイピング)
  3. ABCで作る“守るべき約束”(強制力のある設計図)
    1. 代替実装を安全に差し替える(共通操作を抽象化)
    2. テンプレートメソッドで“流れ”を固定し差分だけ抽象化
  4. Protocolとダックタイピング(柔軟で拡張に強い“構造的”設計)
    1. Protocolで“継承なしでもインターフェース”
    2. ダックタイピングで“動けばOK”をシンプルに
  5. インターフェース的設計の実務パターン(戦略・工場・依存逆転)
    1. 戦略パターン(アルゴリズム差し替えを窓口で統一)
    2. ファクトリー・メソッドで「作り方」をインターフェース化
    3. 依存逆転(上位はインターフェースに依存し、下位が具体化)
  6. 設計のコツ(契約の明確化・最小公開面・テスト容易性)
    1. 契約を“名前・引数・戻り値の意味”まで固定する
    2. 公開面(インターフェース)は最小限に
    3. テストを先に書くと“良いインターフェース”が浮かび上がる
  7. 例題(データエクスポート機能をインターフェースで差し替え可能に)
    1. エクスポータの窓口で形式を切り替える
  8. まとめ(Python流の“インターフェース”はABC・Protocol・ダックタイピングで形にする)

概要(「インターフェース的設計」は“共通の窓口”で差し替え可能にすること)

インターフェースは、異なる実装を“同じやり方で使えるようにする共通の約束”です。PythonにはJavaのようなinterface構文はありませんが、抽象基底クラス(ABC)、ダックタイピング、typing.Protocolで同じ概念を実現できます。目的は、多態性でコードの依存を“やり方(メソッド名・引数)”に揃え、内部の差を外へ漏らさないことです。


基本の考え方(「同じメソッド名で呼べる」ことが設計の核)

インターフェースの要点(統一窓口で多態性を成立させる)

インターフェース的設計は「複数の種類のオブジェクトを統一的に扱う」ための共通規格(メソッド群)を決めることです。たとえば“図形は面積を返せる”“通知は送れる”といった約束を形にすると、呼び手は実装を意識せず同じ手順で使えます Qiita

Pythonでの実現手段(ABC・Protocol・ダックタイピング)

  • ABCは継承で“必須メソッドの実装”を強制できます。
  • Protocolは“構造的型”として、継承なしでも「そのメソッドを持っていればOK」を表現できます。
  • ダックタイピングは型宣言を省き、実際に動くメソッドがあれば受け入れるPython的アプローチです。

ABCで作る“守るべき約束”(強制力のある設計図)

代替実装を安全に差し替える(共通操作を抽象化)

from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, to: str, message: str) -> None:
        """通知は必ず送れる(実装必須)"""
        pass

class EmailNotifier(Notifier):
    def send(self, to: str, message: str) -> None:
        print(f"[EMAIL] to={to} msg={message}")

class SlackNotifier(Notifier):
    def send(self, to: str, message: str) -> None:
        print(f"[SLACK] to={to} msg={message}")

def alert(n: Notifier, user: str, text: str) -> None:
    n.send(user, text)  # 共通の窓口で呼べる
Python
  • “send”という共通インターフェースで束ねると、EmailでもSlackでも同じ呼び方で動きます。ABCは未実装をエラーにするため、実装漏れを防げます。

テンプレートメソッドで“流れ”を固定し差分だけ抽象化

from abc import ABC, abstractmethod

class Exporter(ABC):
    def export(self, data: dict) -> str:
        self._validate(data)
        return self._format(data)

    def _validate(self, data: dict) -> None:
        if "id" not in data: raise ValueError("id必須")

    @abstractmethod
    def _format(self, data: dict) -> str:
        pass

class JsonExporter(Exporter):
    def _format(self, data: dict) -> str:
        import json
        return json.dumps(data, ensure_ascii=False)
Python
  • 共通の検証・流れは親で提供し、フォーマットだけ抽象化すると、拡張が安全かつ一貫します。

Protocolとダックタイピング(柔軟で拡張に強い“構造的”設計)

Protocolで“継承なしでもインターフェース”

from typing import Protocol

class HasArea(Protocol):
    def area(self) -> float: ...

def is_same_area(a: HasArea, b: HasArea) -> bool:
    return abs(a.area() - b.area()) < 1e-9

class Circle:
    def __init__(self, r: float): self.r = r
    def area(self) -> float: return 3.14159 * self.r * self.r

class Rect:
    def __init__(self, w: float, h: float): self.w, self.h = w, h
    def area(self) -> float: return self.w * self.h

print(is_same_area(Circle(1), Rect(3.14159, 1)))  # Trueに近い
Python
  • Protocolは“このメソッドを持っていれば受け入れる”という構造的インターフェース。ライブラリ間の継承を強制せず、後付け拡張にも強いです。

ダックタイピングで“動けばOK”をシンプルに

def notify_all(notifiers, user: str, text: str) -> None:
    for n in notifiers:
        n.send(user, text)  # sendを持っていれば動く(ダックタイピング)
Python
  • 実運用で軽快ですが、型チェックは弱いので、外部境界ではProtocolやABCでガードすると安心です。

インターフェース的設計の実務パターン(戦略・工場・依存逆転)

戦略パターン(アルゴリズム差し替えを窓口で統一)

from abc import ABC, abstractmethod

class PriceRule(ABC):
    @abstractmethod
    def calc(self, amount: float) -> float: ...

class NormalRule(PriceRule):
    def calc(self, amount: float) -> float: return amount

class DiscountRule(PriceRule):
    def calc(self, amount: float) -> float: return amount * 0.9

class Checkout:
    def __init__(self, rule: PriceRule): self.rule = rule
    def total(self, amount: float) -> float: return self.rule.calc(amount)
Python
  • “calc”というインターフェースでアルゴリズムを差し替え可能にすると、拡張が安全になります。

ファクトリー・メソッドで「作り方」をインターフェース化

from abc import ABC, abstractmethod

class ClientFactory(ABC):
    @abstractmethod
    def create(self, base_url: str) -> object: ...

class RequestsFactory(ClientFactory):
    def create(self, base_url: str) -> object:
        import requests
        s = requests.Session(); s.headers.update({"User-Agent": "MyApp"})
        return {"base_url": base_url, "session": s}
Python
  • 生成過程を統一の窓口にまとめると、変更が局所化されます。

依存逆転(上位はインターフェースに依存し、下位が具体化)

上位(サービス)は具体クラスではなくインターフェース(ABC/Protocol)に依存します。これで実装差し替え・テストダブルの注入が容易になり、保守性が向上します。


設計のコツ(契約の明確化・最小公開面・テスト容易性)

契約を“名前・引数・戻り値の意味”まで固定する

名前だけでなく、引数の型と意味、戻り値の構造を明記します。変更は“拡張”で行い、互換性を壊さない(追加引数にデフォルト、戻り値にフィールド追加など)方針が安全です。

公開面(インターフェース)は最小限に

中間状態や内部詳細までインターフェース化すると、依存が増えて壊れやすくなります。利用者が本当に必要な最小のメソッド集合に絞ります。

テストを先に書くと“良いインターフェース”が浮かび上がる

テストダブル(モック・スタブ)を作りやすいインターフェースは、利用者視点で無駄がない証拠です。TDDは“窓口の質”を磨く近道になります。


例題(データエクスポート機能をインターフェースで差し替え可能に)

エクスポータの窓口で形式を切り替える

from abc import ABC, abstractmethod
import json, csv, io

class Exporter(ABC):
    @abstractmethod
    def export(self, rows: list[dict]) -> str: ...

class JsonExporter(Exporter):
    def export(self, rows: list[dict]) -> str:
        return json.dumps(rows, ensure_ascii=False, indent=2)

class CsvExporter(Exporter):
    def export(self, rows: list[dict]) -> str:
        buf = io.StringIO()
        if not rows: return ""
        w = csv.DictWriter(buf, fieldnames=list(rows[0].keys()))
        w.writeheader(); w.writerows(rows)
        return buf.getvalue()

class ExportService:
    def __init__(self, exporter: Exporter): self.exporter = exporter
    def run(self, rows: list[dict]) -> str: return self.exporter.export(rows)

rows = [{"id": 1, "name": "太郎"}, {"id": 2, "name": "花子"}]
print(ExportService(JsonExporter()).run(rows))
print(ExportService(CsvExporter()).run(rows))
Python
  • “export”のインターフェースだけを上位へ公開。形式の追加・差し替えは下位で完結し、呼び手は変更不要です。

まとめ(Python流の“インターフェース”はABC・Protocol・ダックタイピングで形にする)

インターフェース的設計は“同じ窓口で異なる実装を扱う”ための共通約束です。PythonではABCで強制力を持たせ、Protocolで継承なしの柔軟さを確保し、ダックタイピングで軽快に運用できます。契約(名前・シグネチャ・意味)を明確にし、公開面は最小に、上位はインターフェースへ依存。これを型として徹底すれば、初心者でも“差し替えに強く、壊れにくく、テストしやすい”設計を自然に作れるようになります。

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