0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン- Day 29: 総合演習:複数のパターンを組み合わせた設計を体験しよう

Last updated at Posted at 2025-09-21

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第29回目です。これまでの旅で学んだ知識を統合し、より実践的な設計課題に挑戦してみましょう。今回は、複数のパターンを組み合わせて、オンラインストアの注文処理システムを設計します。


課題:オンラインストアの注文処理システム

このシステムは、ユーザーの注文に応じて、以下の機能を実行します。

  • 製品の作成: 注文された製品(書籍、電子機器)を動的に作成します。
  • 価格計算: 会員ステータス(ゴールド会員、一般会員)に応じて、異なる割引を適用します。
  • 配送方法: 複数の配送業者(通常配送、速達)から選択し、その料金を計算します。
  • 注文の履歴: 注文が完了する前の状態を保存し、いつでも元に戻せるようにします。

適用するデザインパターン

  • Factory Method:製品(書籍、電子機器)の生成ロジックをカプセル化し、クライアントコードをシンプルにします。
  • Strategy:価格計算のロジック(割引戦略)を切り替え可能にします。
  • Chain of Responsibility:配送業者を連鎖させ、適切な業者に配送リクエストを処理させます。
  • Memento:注文の状態を保存し、Undo機能を実装します。

Javaでの実装例

// Javaでの実装例

// Factory Method パターン
interface Product {
    String getName();
    double getPrice();
    void showInfo();
}

class Book implements Product {
    private String name = "Programming Book";
    private double price = 3000;
    
    public String getName() { return name; }
    public double getPrice() { return price; }
    public void showInfo() { 
        System.out.println("Book created: " + name + " (¥" + price + ")"); 
    }
}

class Electronics implements Product {
    private String name = "Laptop";
    private double price = 80000;
    
    public String getName() { return name; }
    public double getPrice() { return price; }
    public void showInfo() { 
        System.out.println("Electronics created: " + name + " (¥" + price + ")"); 
    }
}

abstract class ProductFactory {
    public abstract Product createProduct();
}

class BookFactory extends ProductFactory {
    public Product createProduct() { return new Book(); }
}

class ElectronicsFactory extends ProductFactory {
    public Product createProduct() { return new Electronics(); }
}

// Strategy パターン
interface DiscountStrategy {
    double applyDiscount(double price);
    String getDiscountName();
}

class GoldMemberDiscount implements DiscountStrategy {
    public double applyDiscount(double price) { return price * 0.8; } // 20%割引
    public String getDiscountName() { return "ゴールド会員割引(20% OFF)"; }
}

class RegularDiscount implements DiscountStrategy {
    public double applyDiscount(double price) { return price * 0.95; } // 5%割引
    public String getDiscountName() { return "一般会員割引(5% OFF)"; }
}

class PriceCalculator {
    private DiscountStrategy strategy;
    
    public void setDiscountStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public double calculatePrice(double originalPrice) {
        if (strategy == null) return originalPrice;
        return strategy.applyDiscount(originalPrice);
    }
    
    public String getDiscountInfo() {
        return strategy != null ? strategy.getDiscountName() : "割引なし";
    }
}

// Chain of Responsibility パターン
abstract class ShippingHandler {
    protected ShippingHandler nextHandler;
    
    public void setNextHandler(ShippingHandler handler) { 
        this.nextHandler = handler; 
    }
    
    public abstract void handle(String productType);
}

class StandardShipping extends ShippingHandler {
    public void handle(String productType) {
        if (productType.equals("Book")) {
            System.out.println("Standard shipping for Book (配送料: ¥500)");
        } else if (this.nextHandler != null) {
            this.nextHandler.handle(productType);
        } else {
            System.out.println("Cannot handle shipping for: " + productType);
        }
    }
}

class ExpressShipping extends ShippingHandler {
    public void handle(String productType) {
        if (productType.equals("Electronics")) {
            System.out.println("Express shipping for Electronics (配送料: ¥1500)");
        } else if (this.nextHandler != null) {
            this.nextHandler.handle(productType);
        } else {
            System.out.println("Cannot handle shipping for: " + productType);
        }
    }
}

// Memento パターン
class OrderMemento {
    private final String state;
    private final String timestamp;
    
    public OrderMemento(String state) { 
        this.state = state; 
        this.timestamp = java.time.LocalDateTime.now().toString();
    }
    
    public String getSavedState() { return state; }
    public String getTimestamp() { return timestamp; }
}

class OrderOriginator {
    private String state;
    
    public void setState(String state) { this.state = state; }
    public String getState() { return state; }
    public OrderMemento save() { return new OrderMemento(state); }
    public void restore(OrderMemento memento) { 
        this.state = memento.getSavedState(); 
    }
}

class OrderCaretaker {
    private java.util.Stack<OrderMemento> mementos = new java.util.Stack<>();
    
    public void save(OrderMemento memento) { 
        mementos.push(memento); 
    }
    
    public OrderMemento undo() { 
        if (!mementos.isEmpty()) {
            return mementos.pop();
        }
        return null;
    }
    
    public boolean hasUndo() {
        return !mementos.isEmpty();
    }
}

// メインクラスでの統合
public class OnlineStore {
    public static void main(String[] args) {
        System.out.println("=== オンラインストア注文処理システム ===\n");
        
        // Factory Method: 製品の作成
        System.out.println("1. 製品の作成 (Factory Method)");
        ProductFactory bookFactory = new BookFactory();
        ProductFactory electronicsFactory = new ElectronicsFactory();
        
        Product book = bookFactory.createProduct();
        Product electronics = electronicsFactory.createProduct();
        
        book.showInfo();
        electronics.showInfo();
        System.out.println();

        // Strategy: 価格計算
        System.out.println("2. 価格計算 (Strategy)");
        PriceCalculator calculator = new PriceCalculator();
        
        // ゴールド会員の場合
        calculator.setDiscountStrategy(new GoldMemberDiscount());
        double goldPrice = calculator.calculatePrice(book.getPrice());
        System.out.println(calculator.getDiscountInfo() + 
                         ": ¥" + book.getPrice() + " → ¥" + goldPrice);
        
        // 一般会員の場合
        calculator.setDiscountStrategy(new RegularDiscount());
        double regularPrice = calculator.calculatePrice(electronics.getPrice());
        System.out.println(calculator.getDiscountInfo() + 
                         ": ¥" + electronics.getPrice() + " → ¥" + regularPrice);
        System.out.println();

        // Chain of Responsibility: 配送方法
        System.out.println("3. 配送方法の決定 (Chain of Responsibility)");
        ShippingHandler standardShipping = new StandardShipping();
        ShippingHandler expressShipping = new ExpressShipping();
        standardShipping.setNextHandler(expressShipping);
        
        standardShipping.handle("Book");
        standardShipping.handle("Electronics");
        System.out.println();

        // Memento: 注文の履歴
        System.out.println("4. 注文履歴の管理 (Memento)");
        OrderOriginator order = new OrderOriginator();
        OrderCaretaker caretaker = new OrderCaretaker();
        
        order.setState("注文開始");
        System.out.println("現在の状態: " + order.getState());
        caretaker.save(order.save());
        
        order.setState("商品選択完了");
        System.out.println("現在の状態: " + order.getState());
        caretaker.save(order.save());
        
        order.setState("支払い完了");
        System.out.println("現在の状態: " + order.getState());
        
        // Undo操作
        System.out.println("\n--- Undo操作 ---");
        if (caretaker.hasUndo()) {
            OrderMemento memento = caretaker.undo();
            order.restore(memento);
            System.out.println("復元された状態: " + order.getState());
        }
        
        if (caretaker.hasUndo()) {
            OrderMemento memento = caretaker.undo();
            order.restore(memento);
            System.out.println("復元された状態: " + order.getState());
        }
    }
}

Pythonでの実装例

# Pythonでの実装例
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional, List

# Factory Method パターン
class Product(ABC):
    @abstractmethod
    def get_name(self) -> str:
        pass
    
    @abstractmethod
    def get_price(self) -> float:
        pass
    
    @abstractmethod
    def show_info(self) -> None:
        pass

class Book(Product):
    def __init__(self):
        self._name = "Programming Book"
        self._price = 3000
    
    def get_name(self) -> str:
        return self._name
    
    def get_price(self) -> float:
        return self._price
    
    def show_info(self) -> None:
        print(f"Book created: {self._name}{self._price})")

class Electronics(Product):
    def __init__(self):
        self._name = "Laptop"
        self._price = 80000
    
    def get_name(self) -> str:
        return self._name
    
    def get_price(self) -> float:
        return self._price
    
    def show_info(self) -> None:
        print(f"Electronics created: {self._name}{self._price})")

class ProductFactory(ABC):
    @abstractmethod
    def create_product(self) -> Product:
        pass

class BookFactory(ProductFactory):
    def create_product(self) -> Product:
        return Book()

class ElectronicsFactory(ProductFactory):
    def create_product(self) -> Product:
        return Electronics()

# Strategy パターン
class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, price: float) -> float:
        pass
    
    @abstractmethod
    def get_discount_name(self) -> str:
        pass

class GoldMemberDiscount(DiscountStrategy):
    def apply_discount(self, price: float) -> float:
        return price * 0.8  # 20%割引
    
    def get_discount_name(self) -> str:
        return "ゴールド会員割引(20% OFF)"

class RegularDiscount(DiscountStrategy):
    def apply_discount(self, price: float) -> float:
        return price * 0.95  # 5%割引
    
    def get_discount_name(self) -> str:
        return "一般会員割引(5% OFF)"

class PriceCalculator:
    def __init__(self):
        self._strategy: Optional[DiscountStrategy] = None
    
    def set_discount_strategy(self, strategy: DiscountStrategy) -> None:
        self._strategy = strategy
    
    def calculate_price(self, original_price: float) -> float:
        if self._strategy is None:
            return original_price
        return self._strategy.apply_discount(original_price)
    
    def get_discount_info(self) -> str:
        return self._strategy.get_discount_name() if self._strategy else "割引なし"

# Chain of Responsibility パターン
class ShippingHandler(ABC):
    def __init__(self):
        self._next_handler: Optional[ShippingHandler] = None
    
    def set_next_handler(self, handler: 'ShippingHandler') -> None:
        self._next_handler = handler
    
    @abstractmethod
    def handle(self, product_type: str) -> None:
        pass

class StandardShipping(ShippingHandler):
    def handle(self, product_type: str) -> None:
        if product_type == "Book":
            print("Standard shipping for Book (配送料: ¥500)")
        elif self._next_handler:
            self._next_handler.handle(product_type)
        else:
            print(f"Cannot handle shipping for: {product_type}")

class ExpressShipping(ShippingHandler):
    def handle(self, product_type: str) -> None:
        if product_type == "Electronics":
            print("Express shipping for Electronics (配送料: ¥1500)")
        elif self._next_handler:
            self._next_handler.handle(product_type)
        else:
            print(f"Cannot handle shipping for: {product_type}")

# Memento パターン
class OrderMemento:
    def __init__(self, state: str):
        self._state = state
        self._timestamp = datetime.now()
    
    def get_saved_state(self) -> str:
        return self._state
    
    def get_timestamp(self) -> datetime:
        return self._timestamp

class OrderOriginator:
    def __init__(self):
        self._state = ""
    
    def set_state(self, state: str) -> None:
        self._state = state
    
    def get_state(self) -> str:
        return self._state
    
    def save(self) -> OrderMemento:
        return OrderMemento(self._state)
    
    def restore(self, memento: OrderMemento) -> None:
        self._state = memento.get_saved_state()

class OrderCaretaker:
    def __init__(self):
        self._mementos: List[OrderMemento] = []
    
    def save(self, memento: OrderMemento) -> None:
        self._mementos.append(memento)
    
    def undo(self) -> Optional[OrderMemento]:
        if self._mementos:
            return self._mementos.pop()
        return None
    
    def has_undo(self) -> bool:
        return len(self._mementos) > 0

# メインでの統合
def main():
    print("=== オンラインストア注文処理システム ===\n")
    
    # Factory Method: 製品の作成
    print("1. 製品の作成 (Factory Method)")
    book_factory = BookFactory()
    electronics_factory = ElectronicsFactory()
    
    book = book_factory.create_product()
    electronics = electronics_factory.create_product()
    
    book.show_info()
    electronics.show_info()
    print()

    # Strategy: 価格計算
    print("2. 価格計算 (Strategy)")
    calculator = PriceCalculator()
    
    # ゴールド会員の場合
    calculator.set_discount_strategy(GoldMemberDiscount())
    gold_price = calculator.calculate_price(book.get_price())
    print(f"{calculator.get_discount_info()}: ¥{book.get_price()} → ¥{gold_price}")
    
    # 一般会員の場合
    calculator.set_discount_strategy(RegularDiscount())
    regular_price = calculator.calculate_price(electronics.get_price())
    print(f"{calculator.get_discount_info()}: ¥{electronics.get_price()} → ¥{regular_price}")
    print()

    # Chain of Responsibility: 配送方法
    print("3. 配送方法の決定 (Chain of Responsibility)")
    standard_shipping = StandardShipping()
    express_shipping = ExpressShipping()
    standard_shipping.set_next_handler(express_shipping)
    
    standard_shipping.handle("Book")
    standard_shipping.handle("Electronics")
    print()

    # Memento: 注文の履歴
    print("4. 注文履歴の管理 (Memento)")
    order = OrderOriginator()
    caretaker = OrderCaretaker()
    
    order.set_state("注文開始")
    print(f"現在の状態: {order.get_state()}")
    caretaker.save(order.save())
    
    order.set_state("商品選択完了")
    print(f"現在の状態: {order.get_state()}")
    caretaker.save(order.save())
    
    order.set_state("支払い完了")
    print(f"現在の状態: {order.get_state()}")
    
    # Undo操作
    print("\n--- Undo操作 ---")
    if caretaker.has_undo():
        memento = caretaker.undo()
        if memento:
            order.restore(memento)
            print(f"復元された状態: {order.get_state()}")
    
    if caretaker.has_undo():
        memento = caretaker.undo()
        if memento:
            order.restore(memento)
            print(f"復元された状態: {order.get_state()}")

if __name__ == "__main__":
    main()

パターン間の協調関係

この実装では、各パターンが以下のように協調して動作します:

  1. Factory Methodが適切な製品オブジェクトを生成
  2. Strategyがその製品の価格計算方法を決定
  3. Chain of Responsibilityが製品タイプに応じた配送方法を選択
  4. Mementoが注文処理の各段階での状態を保存・復元

実際のシステムでは、これらのパターンがより密接に連携し、一つの統合されたワークフローを形成します。


まとめ

この演習では、一つのシステム内で複数のパターンがどのように連携して複雑な要求を満たすかを確認しました。Factory Methodがオブジェクト生成を、Strategyがアルゴリズムの選択を、Chain of Responsibilityがリクエストの処理を、そしてMementoが状態の管理をそれぞれ担当しています。

重要なポイントは、各パターンが独立して動作しながらも、全体として一つのシステムとして機能することです。このような組み合わせを考えることは、現実のソフトウェア設計において非常に重要です。特定のパターンの使い方だけでなく、それらを組み合わせてより大きなシステムを構築する力を養うことが、デザインパターンを学ぶ究極の目的です。

次回の最終回では、これまでの旅を振り返り、デザインパターンが現代のソフトウェア開発にどう貢献しているか、そしてこれからの展望についてお話しします。お楽しみに!


修正点のまとめ

主要な修正内容

  1. Javaの設計改善

    • Factory Methodパターンで抽象クラスを使用
    • 製品クラスに価格情報を追加
    • Strategyパターンでコンテキストクラス(PriceCalculator)を追加
    • Chain of Responsibilityで抽象基底クラスを使用
    • Mementoパターンでスタック構造による履歴管理
  2. Pythonの設計改善

    • 適切な抽象基底クラス(ABC)の使用
    • 型ヒントの追加
    • Factory Methodパターンの正しい実装
    • Chain of Responsibilityの改善
  3. 実用性の向上

    • より詳細な出力メッセージ
    • 実際の価格計算例の追加
    • 複数のUndo操作のサポート
    • エラーハンドリングの改善
  4. 教育効果の向上

    • パターン間の協調関係の説明を追加
    • 実行結果の可視化
    • コードの可読性向上
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?