Chapter 25: Exercises — Design Patterns and Clean Code

These 30 exercises are organized into five tiers of increasing difficulty. Each tier builds on the skills developed in previous tiers.


Tier 1: Recall and Recognition (Exercises 1–6)

Exercise 1: Pattern Identification

Read the following code snippets and identify which design pattern each one implements:

Snippet A:

class Logger:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Snippet B:

handlers = {"csv": parse_csv, "json": parse_json, "xml": parse_xml}
def parse(format_type, data):
    return handlers[format_type](data)

Snippet C:

class OldAPI:
    def fetch_data(self, query):
        ...

class NewAPIAdapter:
    def __init__(self, old_api):
        self._api = old_api
    def get(self, params):
        return self._api.fetch_data(params["query"])

Task: Name the pattern for each snippet and explain your reasoning in 2–3 sentences per snippet.


Exercise 2: Clean Code Principles Matching

Match each clean code principle to its definition:

Principle Definition
A. DRY 1. Functions should do one thing and do it well
B. KISS 2. Leave the code better than you found it
C. SRP 3. Every piece of knowledge should have a single representation
D. Boy Scout Rule 4. Choose the simplest solution that works
E. YAGNI 5. Don't build features until they are needed

Exercise 3: Code Smell Identification

Identify at least three code smells in the following function:

def f(d, t, s, m, r):
    # process the data
    result = []
    for i in range(len(d)):
        if d[i]["type"] == t:
            if d[i]["status"] == s:
                if d[i]["mode"] == m:
                    x = d[i]["value"] * r
                    result.append({"id": d[i]["id"], "value": x, "type": t, "status": s, "mode": m})
    return result

List each smell and explain why it is a problem.


Exercise 4: Protocol vs ABC

Explain the difference between typing.Protocol and abc.ABC in Python. Give one scenario where each would be the better choice. Write a short code example for each (5–10 lines).


Exercise 5: Pattern Vocabulary

For each of the following scenarios, name the most appropriate design pattern:

  1. You need to convert data from an old system's format to work with a new API.
  2. You want to add logging to every method call without modifying existing classes.
  3. Different shipping carriers each have their own rate calculation formula.
  4. A spreadsheet application needs to recalculate formulas when cell values change.
  5. You are building SQL queries step by step with optional clauses.
  6. You want a simple interface to a complex image-processing library.

Exercise 6: Pythonic vs Non-Pythonic

Rewrite each Java-style Python snippet in idiomatic Python:

Snippet A:

class StringUtils:
    @staticmethod
    def is_empty(s):
        if s is None:
            return True
        if len(s) == 0:
            return True
        return False

Snippet B:

class NumberList:
    def __init__(self):
        self.numbers = []

    def addNumber(self, number):
        self.numbers.append(number)

    def getSum(self):
        total = 0
        for i in range(len(self.numbers)):
            total = total + self.numbers[i]
        return total

Tier 2: Understanding and Application (Exercises 7–12)

Exercise 7: Simple Factory Implementation

Create a factory function create_notification that produces different notification objects based on a channel parameter. Support "email", "sms", and "push". Each notification type should have a send(recipient: str, message: str) -> bool method.

Requirements: - Use a Notification Protocol - Use dataclasses for each concrete type - Use dictionary dispatch in the factory - Include type hints throughout


Exercise 8: Decorator Pattern — Both Kinds

Implement both forms of the Decorator pattern:

Part A: Write a Python decorator (function-based) called @timed that logs the execution time of any function.

Part B: Write a GoF-style Decorator pattern with a MessageProcessor protocol and concrete decorators EncryptionDecorator and CompressionDecorator that can be stacked.


Exercise 9: Observer with Type Safety

Implement an EventBus class that: - Allows subscribing to events by event type (using the class of a dataclass) - Dispatches events only to handlers registered for that specific event type - Uses type hints throughout - Includes an unsubscribe method

Test it with at least two different event types.


Exercise 10: Clean Code Refactoring

Refactor the following code to follow clean code principles. Preserve the exact behavior.

def do_stuff(data):
    r = []
    for d in data:
        if d["t"] == "A":
            v = d["v"] * 1.1
            if v > 100:
                v = 100
            r.append({"id": d["id"], "val": v, "cat": "premium"})
        elif d["t"] == "B":
            v = d["v"] * 0.9
            if v < 0:
                v = 0
            r.append({"id": d["id"], "val": v, "cat": "standard"})
        elif d["t"] == "C":
            v = d["v"] * 0.5
            r.append({"id": d["id"], "val": v, "cat": "discount"})
    return r

Your refactored version should: - Use descriptive names - Extract functions where appropriate - Eliminate duplication - Add type hints


Exercise 11: Builder for Configuration

Create a ServerConfigBuilder class that builds a ServerConfig dataclass. The builder should support: - Setting host and port - Adding middleware (list of strings) - Setting SSL certificate and key paths - Setting environment (development, staging, production) - A build() method that validates the configuration (production requires SSL, port must be > 0)


Exercise 12: Facade Pattern

Design a ReportGenerator facade that simplifies the following subsystem:

class DataFetcher:
    def fetch(self, query: str) -> list[dict]: ...

class DataCleaner:
    def remove_nulls(self, data: list[dict]) -> list[dict]: ...
    def normalize(self, data: list[dict], columns: list[str]) -> list[dict]: ...

class StatisticsEngine:
    def compute_summary(self, data: list[dict]) -> dict: ...
    def compute_correlations(self, data: list[dict], columns: list[str]) -> dict: ...

class ChartRenderer:
    def bar_chart(self, data: dict, title: str) -> str: ...
    def line_chart(self, data: list[dict], x: str, y: str) -> str: ...

class PDFExporter:
    def create_document(self, title: str) -> None: ...
    def add_text(self, text: str) -> None: ...
    def add_chart(self, chart: str) -> None: ...
    def export(self, filename: str) -> str: ...

Your facade should have a single generate_report(query, title, filename) method.


Tier 3: Analysis and Integration (Exercises 13–18)

Exercise 13: Strategy Pattern with Registration

Extend the Strategy pattern to support dynamic registration of strategies at runtime. Create a TextAnalyzer class that: - Has a registry of analysis strategies (word count, sentence count, readability score, keyword extraction) - Allows registering new strategies via a register_strategy class method - Can run all registered strategies or a selected subset on a given text - Returns results as a dictionary mapping strategy names to results

Write at least four concrete strategies and demonstrate dynamic registration.


Exercise 14: Command Pattern with Macro Support

Extend the Command pattern from the chapter to support macros: - A Macro class that groups multiple commands into a single undoable unit - Executing a macro runs all its commands in order - Undoing a macro reverses all its commands in reverse order - A CommandHistory that treats macros as single entries

Demonstrate with a text editor scenario where "Format Paragraph" is a macro of multiple insert/delete operations.


Exercise 15: Adapter for Multiple APIs

You are integrating with three different weather APIs, each with a different interface:

class WeatherAPI_V1:
    def get_weather(self, city: str) -> dict:
        return {"temp_f": 72, "humidity": 45, "wind_mph": 10}

class WeatherAPI_V2:
    def fetch(self, lat: float, lon: float) -> str:
        return '{"temperature_c": 22, "humidity_pct": 45, "wind_kmh": 16}'

class WeatherAPI_V3:
    def query(self, location: dict) -> list:
        return [{"metric": "temp", "value": 295.15, "unit": "K"},
                {"metric": "humidity", "value": 45, "unit": "%"},
                {"metric": "wind", "value": 4.5, "unit": "m/s"}]

Create a unified WeatherService protocol and write adapters for each API. All adapters should return temperature in Celsius, humidity as a percentage, and wind speed in km/h.


Exercise 16: Code Smell Refactoring Marathon

The following class has at least six code smells. Identify all of them and refactor the class:

class OrderManager:
    def __init__(self):
        self.orders = []
        self.db_host = "localhost"
        self.db_port = 5432
        self.db_name = "orders"
        self.email_host = "smtp.example.com"
        self.email_port = 587
        self.email_user = "noreply@example.com"
        self.tax_rate = 0.08
        self.discount_threshold = 100
        self.discount_rate = 0.1

    def process(self, order_data):
        # Validate
        if not order_data.get("items"):
            return {"error": "No items"}
        if not order_data.get("customer_email"):
            return {"error": "No email"}

        # Calculate
        total = 0
        for item in order_data["items"]:
            total += item["price"] * item["qty"]

        # Tax
        total_with_tax = total + (total * self.tax_rate)

        # Discount
        if total_with_tax > self.discount_threshold:
            total_with_tax = total_with_tax - (total_with_tax * self.discount_rate)

        # Save to database
        import psycopg2
        conn = psycopg2.connect(host=self.db_host, port=self.db_port, dbname=self.db_name)
        cur = conn.cursor()
        cur.execute("INSERT INTO orders (email, total) VALUES (%s, %s)",
                     (order_data["customer_email"], total_with_tax))
        conn.commit()
        conn.close()

        # Send email
        import smtplib
        from email.mime.text import MIMEText
        msg = MIMEText(f"Your order total is ${total_with_tax:.2f}")
        msg["Subject"] = "Order Confirmation"
        msg["From"] = self.email_user
        msg["To"] = order_data["customer_email"]
        server = smtplib.SMTP(self.email_host, self.email_port)
        server.send_message(msg)
        server.quit()

        return {"success": True, "total": total_with_tax}

Exercise 17: AI-Specific Patterns

Implement a complete PromptChain class that: - Takes a list of PromptTemplate objects (from Section 25.9) - Passes the output of each prompt as input to the next - Supports branching (different next templates based on the output) - Includes error handling for each step - Logs each step's input and output

This implements the "chain of thought" pattern at the application level.


Exercise 18: Pattern Comparison

For the following requirements, implement the solution using two different patterns and compare them. Write 3–5 sentences analyzing the trade-offs.

Requirement: A drawing application supports multiple shapes (circle, rectangle, triangle). Each shape can be drawn, resized, and exported to SVG. New shape types may be added in the future.

Implement using: 1. The Strategy pattern (shape operations as interchangeable strategies) 2. The Template Method pattern (base class with overridable steps)


Tier 4: Synthesis and Design (Exercises 19–24)

Exercise 19: Plugin Architecture

Design and implement a plugin system using Factory and Observer patterns: - A PluginRegistry (Factory) that discovers and instantiates plugins - An EventBus (Observer) that plugins use to communicate - A Plugin protocol defining the lifecycle methods: initialize(), execute(context), shutdown() - At least three example plugins - A PluginManager that coordinates the full lifecycle

Write comprehensive docstrings and type hints.


Exercise 20: Mini Framework

Build a minimal web-framework-like request handler using multiple patterns: - Factory for creating request handlers based on URL patterns - Strategy for content negotiation (JSON, XML, HTML responses) - Decorator for middleware (authentication, logging, CORS) - Command for request processing pipeline

The framework should handle at least three different routes.


Exercise 21: AI Conversation System

Build a complete conversation system using the patterns from Section 25.9: - PromptTemplate for generating prompts - ConversationManager for managing multi-turn context - OutputParser for extracting structured data - RetryWithFallback for resilience

Add a ConversationPersistence layer that can save/load conversations to JSON files.


Exercise 22: Refactoring Challenge

You are given a 100-line monolithic function (provided below in a simplified form). Refactor it into a well-structured system using at least three design patterns. Document each pattern choice and why it fits.

def run_etl_pipeline(source_type, source_config, transformations, destination_type, dest_config):
    # Read data
    if source_type == "csv":
        import csv
        with open(source_config["path"]) as f:
            data = list(csv.DictReader(f))
    elif source_type == "json":
        import json
        with open(source_config["path"]) as f:
            data = json.load(f)
    elif source_type == "api":
        import requests
        resp = requests.get(source_config["url"], headers=source_config.get("headers", {}))
        data = resp.json()

    # Transform data
    for t in transformations:
        if t["type"] == "filter":
            data = [row for row in data if row.get(t["field"]) == t["value"]]
        elif t["type"] == "rename":
            data = [{t["new"]: row.pop(t["old"], None), **row} for row in data]
        elif t["type"] == "calculate":
            for row in data:
                row[t["output"]] = eval(t["expression"], {"row": row})
        elif t["type"] == "drop_nulls":
            data = [row for row in data if all(v is not None for v in row.values())]

    # Write data
    if destination_type == "csv":
        import csv
        with open(dest_config["path"], "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=data[0].keys())
            writer.writeheader()
            writer.writerows(data)
    elif destination_type == "json":
        import json
        with open(dest_config["path"], "w") as f:
            json.dump(data, f, indent=2)
    elif destination_type == "database":
        pass  # database insertion logic

    return len(data)

Exercise 23: Pattern Detection in Open Source

Choose a well-known Python open-source project (e.g., Flask, Django, Requests, FastAPI). Identify at least five design patterns used in the project. For each pattern: 1. Name the pattern 2. Locate the specific code (file and approximate line numbers) 3. Explain why the pattern is used in that context 4. Evaluate whether the pattern is the right choice

Write your analysis as a 500-word report.


Exercise 24: Clean Code Metrics

Write a Python script that analyzes a Python source file and reports on clean code metrics: - Average function length (in lines) - Maximum function length - Number of functions with more than 4 parameters - Percentage of functions with docstrings - Number of single-letter variable names - Cyclomatic complexity estimate (count of if, elif, for, while, and, or per function)

Use the ast module for parsing. Test it on your own code from previous exercises.


Tier 5: Expert Challenges (Exercises 25–30)

Exercise 25: Pattern Language

Design a "pattern language" for AI-powered applications. Define at least eight patterns that work together to address common concerns in AI applications: - Input validation and sanitization - Prompt construction - Model selection and fallback - Output parsing and validation - Caching and memoization - Rate limiting - Conversation state management - Error recovery

For each pattern, provide: Name, Intent, Motivation, Structure (class diagram or code), and Example usage. Show how the patterns connect to each other.


Exercise 26: Meta-Pattern Generator

Write a Python metaprogramming tool that generates pattern implementations from a declarative specification:

spec = {
    "pattern": "observer",
    "subject": "StockMarket",
    "events": ["price_changed", "market_opened", "market_closed"],
    "observers": [
        {"name": "TradingBot", "subscribes_to": ["price_changed"]},
        {"name": "Dashboard", "subscribes_to": ["price_changed", "market_opened", "market_closed"]},
        {"name": "AlertService", "subscribes_to": ["price_changed"]},
    ],
}

Given this spec, your tool should generate complete, working Python classes with type hints and docstrings.


Exercise 27: Anti-Pattern Catalog

Create a catalog of at least eight "anti-patterns" specific to AI-assisted development. For each anti-pattern: - Name it creatively (e.g., "Copy-Paste-Pray") - Describe the problem - Explain why AI assistants tend to generate it - Provide a before/after code example - Suggest the correct pattern or principle to apply


Exercise 28: Performance-Aware Patterns

Some patterns introduce overhead (extra function calls, object creation, indirection). Write benchmarks comparing: 1. Strategy as a function vs. Strategy as a class with __call__ 2. Observer with list-based dispatch vs. dictionary-based dispatch 3. Factory with dictionary lookup vs. if-elif chain 4. Decorator chain (3 decorators) vs. a single combined function

Use Python's timeit module. Present results and analyze when the overhead matters.


Exercise 29: Domain-Specific Pattern

Choose a specific domain (e.g., e-commerce, healthcare, game development, data pipeline) and design three domain-specific patterns that extend the GoF patterns for that domain. For each: - Define the pattern with a name, intent, and structure - Show a complete Python implementation - Explain how it differs from the base GoF pattern - Provide a realistic usage scenario


Exercise 30: Full Application

Build a complete command-line application (200+ lines) that demonstrates at least six different design patterns working together. The application should be a "Task Automation Engine" that: - Reads task definitions from JSON files (Builder) - Creates task handlers based on type (Factory) - Processes tasks through a pipeline (Command + Chain of Responsibility) - Notifies subscribers on task completion (Observer) - Supports different output formats (Strategy) - Provides a simple high-level API (Facade)

Include comprehensive tests, type hints, and docstrings. The code should pass mypy --strict and ruff check.