jpskill.com
🛠️ 開発・MCP コミュニティ 🔴 エンジニア向け 👤 エンジニア・AI開発者

🛠️ PythonDesignパターン集

python-design-patterns

KISS、関心の分離、単一責任の原則、継承よりコンポジションといったPythonのデザインパターンを用いて、アーキテクチャの決定やコードのリファクタリング、抽象化の評価を行うためのSkill。

⏱ コードレビュー 1時間 → 10分
📜 元の英語説明(参考)

Python design patterns including KISS, Separation of Concerns, Single Responsibility, and composition over inheritance. Use when making architecture decisions, refactoring code structure, or evaluating when abstractions are appropriate.

🇯🇵 日本人クリエイター向け解説

一言でいうと

KISS、関心の分離、単一責任の原則、継承よりコンポジションといったPythonのデザインパターンを用いて、アーキテクチャの決定やコードのリファクタリング、抽象化の評価を行うためのSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o python-design-patterns.zip https://jpskill.com/download/5272.zip && unzip -o python-design-patterns.zip && rm python-design-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/5272.zip -OutFile "$d\python-design-patterns.zip"; Expand-Archive "$d\python-design-patterns.zip" -DestinationPath $d -Force; ri "$d\python-design-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して python-design-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → python-design-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-18
同梱ファイル
1

💬 こう話しかけるだけ — サンプルプロンプト

  • Python Design Patterns を使って、最小構成のサンプルコードを示して
  • Python Design Patterns の主な使い方と注意点を教えて
  • Python Design Patterns を既存プロジェクトに組み込む方法を教えて

これをClaude Code に貼るだけで、このSkillが自動発動します。

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Pythonデザインパターン

基本的な設計原則を用いて、保守しやすいPythonコードを記述します。これらのパターンは、理解しやすく、テストしやすく、修正しやすいシステムを構築するのに役立ちます。

このスキルを使用するタイミング

  • 新しいコンポーネントやサービスを設計する際
  • 複雑なコードや絡み合ったコードをリファクタリングする際
  • 抽象化を作成するかどうかを決定する際
  • 継承とコンポジションのどちらを選択するかを決定する際
  • コードの複雑さと結合度を評価する際
  • モジュール式のアーキテクチャを計画する際

コアコンセプト

1. KISS (Keep It Simple)

機能する最もシンプルなソリューションを選択してください。複雑さは具体的な要件によって正当化されなければなりません。

2. 単一責任 (SRP)

各ユニットは変更する理由を1つだけ持つべきです。関心事を集中したコンポーネントに分離してください。

3. 継承よりもコンポジション

クラスを拡張するのではなく、オブジェクトを組み合わせて振る舞いを構築してください。

4. スリーの法則 (Rule of Three)

3つのインスタンスができるまで抽象化を待ってください。多くの場合、時期尚早な抽象化よりも重複の方が優れています。

クイックスタート

# Simple beats clever
# Instead of a factory/registry pattern:
FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}

def get_formatter(name: str) -> Formatter:
    return FORMATTERS[name]()

基本パターン

パターン1: KISS - Keep It Simple

複雑さを追加する前に、よりシンプルなソリューションが機能するかどうかを自問してください。

# Over-engineered: Factory with registration
class OutputFormatterFactory:
    _formatters: dict[str, type[Formatter]] = {}

    @classmethod
    def register(cls, name: str):
        def decorator(formatter_cls):
            cls._formatters[name] = formatter_cls
            return formatter_cls
        return decorator

    @classmethod
    def create(cls, name: str) -> Formatter:
        return cls._formatters[name]()

@OutputFormatterFactory.register("json")
class JsonFormatter(Formatter):
    ...

# Simple: Just use a dictionary
FORMATTERS = {
    "json": JsonFormatter,
    "csv": CsvFormatter,
    "xml": XmlFormatter,
}

def get_formatter(name: str) -> Formatter:
    """Get formatter by name."""
    if name not in FORMATTERS:
        raise ValueError(f"Unknown format: {name}")
    return FORMATTERS[name]()

ファクトリーパターンは、ここでは価値を追加せずにコードを追加しています。パターンは、実際の問題を解決するときのために温存してください。

パターン2: 単一責任の原則

各クラスまたは関数は、変更する理由を1つだけ持つべきです。

# BAD: Handler does everything
class UserHandler:
    async def create_user(self, request: Request) -> Response:
        # HTTP parsing
        data = await request.json()

        # Validation
        if not data.get("email"):
            return Response({"error": "email required"}, status=400)

        # Database access
        user = await db.execute(
            "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
            data["email"], data["name"]
        )

        # Response formatting
        return Response({"id": user.id, "email": user.email}, status=201)

# GOOD: Separated concerns
class UserService:
    """Business logic only."""

    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo

    async def create_user(self, data: CreateUserInput) -> User:
        # Only business rules here
        user = User(email=data.email, name=data.name)
        return await self._repo.save(user)

class UserHandler:
    """HTTP concerns only."""

    def __init__(self, service: UserService) -> None:
        self._service = service

    async def create_user(self, request: Request) -> Response:
        data = CreateUserInput(**(await request.json()))
        user = await self._service.create_user(data)
        return Response(user.to_dict(), status=201)

これで、HTTPの変更がビジネスロジックに影響を与えず、その逆も同様です。

パターン3: 関心事の分離

コードを明確な責任を持つ個別のレイヤーに整理します。

┌─────────────────────────────────────────────────────┐
│  APIレイヤー (ハンドラー)                            │
│  - リクエストの解析                                  │
│  - サービスの呼び出し                                │
│  - レスポンスのフォーマット                          │
└─────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────┐
│  サービスレイヤー (ビジネスロジック)                 │
│  - ドメインルールとバリデーション                    │
│  - 操作のオーケストレーション                        │
│  - 可能な限り純粋関数                                │
└─────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────┐
│  リポジトリレイヤー (データアクセス)                 │
│  - SQLクエリ                                         │
│  - 外部API呼び出し                                   │
│  - キャッシュ操作                                    │
└─────────────────────────────────────────────────────┘

各レイヤーは、その下にあるレイヤーにのみ依存します。

# Repository: Data access
class UserRepository:
    async def get_by_id(self, user_id: str) -> User | None:
        row = await self._db.fetchrow(
            "SELECT * FROM users WHERE id = $1", user_id
        )
        return User(**row) if row else None

# Service: Business logic
class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo

    async def get_user(self, user_id: str) -> User:
        user = await self._repo.get_by_id(user_id)
        if user is None:
            raise UserNotFoundError(user_id)
        return user

# Handler: HTTP concerns
@app.get("/users/{user_id}")
async def get_user(user_id: str) -> UserResponse:
    user = await user_service.get_user(user_id)
    return UserResponse.from_user(user)

パターン4: 継承よりもコンポジション

継承するのではなく、オブジェクトを組み合わせて振る舞いを構築します。

# Inheritance: Rigid and hard to test
class EmailNotificationService(Notifica
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Python Design Patterns

Write maintainable Python code using fundamental design principles. These patterns help you build systems that are easy to understand, test, and modify.

When to Use This Skill

  • Designing new components or services
  • Refactoring complex or tangled code
  • Deciding whether to create an abstraction
  • Choosing between inheritance and composition
  • Evaluating code complexity and coupling
  • Planning modular architectures

Core Concepts

1. KISS (Keep It Simple)

Choose the simplest solution that works. Complexity must be justified by concrete requirements.

2. Single Responsibility (SRP)

Each unit should have one reason to change. Separate concerns into focused components.

3. Composition Over Inheritance

Build behavior by combining objects, not extending classes.

4. Rule of Three

Wait until you have three instances before abstracting. Duplication is often better than premature abstraction.

Quick Start

# Simple beats clever
# Instead of a factory/registry pattern:
FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}

def get_formatter(name: str) -> Formatter:
    return FORMATTERS[name]()

Fundamental Patterns

Pattern 1: KISS - Keep It Simple

Before adding complexity, ask: does a simpler solution work?

# Over-engineered: Factory with registration
class OutputFormatterFactory:
    _formatters: dict[str, type[Formatter]] = {}

    @classmethod
    def register(cls, name: str):
        def decorator(formatter_cls):
            cls._formatters[name] = formatter_cls
            return formatter_cls
        return decorator

    @classmethod
    def create(cls, name: str) -> Formatter:
        return cls._formatters[name]()

@OutputFormatterFactory.register("json")
class JsonFormatter(Formatter):
    ...

# Simple: Just use a dictionary
FORMATTERS = {
    "json": JsonFormatter,
    "csv": CsvFormatter,
    "xml": XmlFormatter,
}

def get_formatter(name: str) -> Formatter:
    """Get formatter by name."""
    if name not in FORMATTERS:
        raise ValueError(f"Unknown format: {name}")
    return FORMATTERS[name]()

The factory pattern adds code without adding value here. Save patterns for when they solve real problems.

Pattern 2: Single Responsibility Principle

Each class or function should have one reason to change.

# BAD: Handler does everything
class UserHandler:
    async def create_user(self, request: Request) -> Response:
        # HTTP parsing
        data = await request.json()

        # Validation
        if not data.get("email"):
            return Response({"error": "email required"}, status=400)

        # Database access
        user = await db.execute(
            "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
            data["email"], data["name"]
        )

        # Response formatting
        return Response({"id": user.id, "email": user.email}, status=201)

# GOOD: Separated concerns
class UserService:
    """Business logic only."""

    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo

    async def create_user(self, data: CreateUserInput) -> User:
        # Only business rules here
        user = User(email=data.email, name=data.name)
        return await self._repo.save(user)

class UserHandler:
    """HTTP concerns only."""

    def __init__(self, service: UserService) -> None:
        self._service = service

    async def create_user(self, request: Request) -> Response:
        data = CreateUserInput(**(await request.json()))
        user = await self._service.create_user(data)
        return Response(user.to_dict(), status=201)

Now HTTP changes don't affect business logic, and vice versa.

Pattern 3: Separation of Concerns

Organize code into distinct layers with clear responsibilities.

┌─────────────────────────────────────────────────────┐
│  API Layer (handlers)                                │
│  - Parse requests                                    │
│  - Call services                                     │
│  - Format responses                                  │
└─────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────┐
│  Service Layer (business logic)                      │
│  - Domain rules and validation                       │
│  - Orchestrate operations                            │
│  - Pure functions where possible                     │
└─────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────┐
│  Repository Layer (data access)                      │
│  - SQL queries                                       │
│  - External API calls                                │
│  - Cache operations                                  │
└─────────────────────────────────────────────────────┘

Each layer depends only on layers below it:

# Repository: Data access
class UserRepository:
    async def get_by_id(self, user_id: str) -> User | None:
        row = await self._db.fetchrow(
            "SELECT * FROM users WHERE id = $1", user_id
        )
        return User(**row) if row else None

# Service: Business logic
class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo

    async def get_user(self, user_id: str) -> User:
        user = await self._repo.get_by_id(user_id)
        if user is None:
            raise UserNotFoundError(user_id)
        return user

# Handler: HTTP concerns
@app.get("/users/{user_id}")
async def get_user(user_id: str) -> UserResponse:
    user = await user_service.get_user(user_id)
    return UserResponse.from_user(user)

Pattern 4: Composition Over Inheritance

Build behavior by combining objects rather than inheriting.

# Inheritance: Rigid and hard to test
class EmailNotificationService(NotificationService):
    def __init__(self):
        super().__init__()
        self._smtp = SmtpClient()  # Hard to mock

    def notify(self, user: User, message: str) -> None:
        self._smtp.send(user.email, message)

# Composition: Flexible and testable
class NotificationService:
    """Send notifications via multiple channels."""

    def __init__(
        self,
        email_sender: EmailSender,
        sms_sender: SmsSender | None = None,
        push_sender: PushSender | None = None,
    ) -> None:
        self._email = email_sender
        self._sms = sms_sender
        self._push = push_sender

    async def notify(
        self,
        user: User,
        message: str,
        channels: set[str] | None = None,
    ) -> None:
        channels = channels or {"email"}

        if "email" in channels:
            await self._email.send(user.email, message)

        if "sms" in channels and self._sms and user.phone:
            await self._sms.send(user.phone, message)

        if "push" in channels and self._push and user.device_token:
            await self._push.send(user.device_token, message)

# Easy to test with fakes
service = NotificationService(
    email_sender=FakeEmailSender(),
    sms_sender=FakeSmsSender(),
)

Advanced Patterns

Pattern 5: Rule of Three

Wait until you have three instances before abstracting.

# Two similar functions? Don't abstract yet
def process_orders(orders: list[Order]) -> list[Result]:
    results = []
    for order in orders:
        validated = validate_order(order)
        result = process_validated_order(validated)
        results.append(result)
    return results

def process_returns(returns: list[Return]) -> list[Result]:
    results = []
    for ret in returns:
        validated = validate_return(ret)
        result = process_validated_return(validated)
        results.append(result)
    return results

# These look similar, but wait! Are they actually the same?
# Different validation, different processing, different errors...
# Duplication is often better than the wrong abstraction

# Only after a third case, consider if there's a real pattern
# But even then, sometimes explicit is better than abstract

Pattern 6: Function Size Guidelines

Keep functions focused. Extract when a function:

  • Exceeds 20-50 lines (varies by complexity)
  • Serves multiple distinct purposes
  • Has deeply nested logic (3+ levels)
# Too long, multiple concerns mixed
def process_order(order: Order) -> Result:
    # 50 lines of validation...
    # 30 lines of inventory check...
    # 40 lines of payment processing...
    # 20 lines of notification...
    pass

# Better: Composed from focused functions
def process_order(order: Order) -> Result:
    """Process a customer order through the complete workflow."""
    validate_order(order)
    reserve_inventory(order)
    payment_result = charge_payment(order)
    send_confirmation(order, payment_result)
    return Result(success=True, order_id=order.id)

Pattern 7: Dependency Injection

Pass dependencies through constructors for testability.

from typing import Protocol

class Logger(Protocol):
    def info(self, msg: str, **kwargs) -> None: ...
    def error(self, msg: str, **kwargs) -> None: ...

class Cache(Protocol):
    async def get(self, key: str) -> str | None: ...
    async def set(self, key: str, value: str, ttl: int) -> None: ...

class UserService:
    """Service with injected dependencies."""

    def __init__(
        self,
        repository: UserRepository,
        cache: Cache,
        logger: Logger,
    ) -> None:
        self._repo = repository
        self._cache = cache
        self._logger = logger

    async def get_user(self, user_id: str) -> User:
        # Check cache first
        cached = await self._cache.get(f"user:{user_id}")
        if cached:
            self._logger.info("Cache hit", user_id=user_id)
            return User.from_json(cached)

        # Fetch from database
        user = await self._repo.get_by_id(user_id)
        if user:
            await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)

        return user

# Production
service = UserService(
    repository=PostgresUserRepository(db),
    cache=RedisCache(redis),
    logger=StructlogLogger(),
)

# Testing
service = UserService(
    repository=InMemoryUserRepository(),
    cache=FakeCache(),
    logger=NullLogger(),
)

Pattern 8: Avoiding Common Anti-Patterns

Don't expose internal types:

# BAD: Leaking ORM model to API
@app.get("/users/{id}")
def get_user(id: str) -> UserModel:  # SQLAlchemy model
    return db.query(UserModel).get(id)

# GOOD: Use response schemas
@app.get("/users/{id}")
def get_user(id: str) -> UserResponse:
    user = db.query(UserModel).get(id)
    return UserResponse.from_orm(user)

Don't mix I/O with business logic:

# BAD: SQL embedded in business logic
def calculate_discount(user_id: str) -> float:
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
    # Business logic mixed with data access

# GOOD: Repository pattern
def calculate_discount(user: User, order_history: list[Order]) -> float:
    # Pure business logic, easily testable
    if len(order_history) > 10:
        return 0.15
    return 0.0

Best Practices Summary

  1. Keep it simple - Choose the simplest solution that works
  2. Single responsibility - Each unit has one reason to change
  3. Separate concerns - Distinct layers with clear purposes
  4. Compose, don't inherit - Combine objects for flexibility
  5. Rule of three - Wait before abstracting
  6. Keep functions small - 20-50 lines (varies by complexity), one purpose
  7. Inject dependencies - Constructor injection for testability
  8. Delete before abstracting - Remove dead code, then consider patterns
  9. Test each layer - Isolated tests for each concern
  10. Explicit over clever - Readable code beats elegant code