練習課題
概要
本課題では、購入処理において 在庫確認 → 支払い処理 → 領収書発行
の一連の処理を
呼び出し元関数 completePurchase()
がすべて自前で組み立てている構造に対して、
責務分散と抽象化による設計改善を検討する。
課題コード
以下は、複数のサービスクラスを使って商品購入の処理を構成しているが、
処理の流れがすべて呼び出し元関数に露出しており、再利用・テスト・変更に対して柔軟性を欠いている。
- TypeScript
- PHP
- Python
class InventoryChecker {
check(productId: string): boolean {
console.log(`在庫確認: ${productId}`);
return true;
}
}
class PaymentGateway {
pay(amount: number): boolean {
console.log(`支払い処理: ¥${amount}`);
return true;
}
}
class ReceiptPrinter {
print(productId: string, amount: number) {
console.log(`領収書発行: 商品=${productId}, 金額=¥${amount}`);
}
}
function completePurchase(productId: string, amount: number) {
const checker = new InventoryChecker();
const payment = new PaymentGateway();
const printer = new ReceiptPrinter();
if (checker.check(productId)) {
if (payment.pay(amount)) {
printer.print(productId, amount);
}
}
}
<?php
class InventoryChecker {
public function check(string $productId): bool {
echo "在庫確認: {$productId}\n";
return true;
}
}
class PaymentGateway {
public function pay(int $amount): bool {
echo "支払い処理: ¥{$amount}\n";
return true;
}
}
class ReceiptPrinter {
public function print(string $productId, int $amount): void {
echo "領収書発行: 商品={$productId}, 金額=¥{$amount}\n";
}
}
function completePurchase(string $productId, int $amount): void {
$checker = new InventoryChecker();
$payment = new PaymentGateway();
$printer = new ReceiptPrinter();
if ($checker->check($productId)) {
if ($payment->pay($amount)) {
$printer->print($productId, $amount);
}
}
}
class InventoryChecker:
def check(self, product_id: str) -> bool:
print(f"在庫確認: {product_id}")
return True
class PaymentGateway:
def pay(self, amount: int) -> bool:
print(f"支払い処理: ¥{amount}")
return True
class ReceiptPrinter:
def print(self, product_id: str, amount: int):
print(f"領収書発行: 商品={product_id}, 金額=¥{amount}")
def complete_purchase(product_id: str, amount: int):
checker = InventoryChecker()
payment = PaymentGateway()
printer = ReceiptPrinter()
if checker.check(product_id):
if payment.pay(amount):
printer.print(product_id, amount)
設問 1:このコードが抱える設計上の問題点を挙げよ
以下の観点を参考に、具体的に列挙すること:
- 呼び出し元が複数のクラスを知っており、依存関係が強すぎる
- 処理の順序・条件分岐・例外処理が一箇所に集中している
- ロジックの再利用や流れの切り替えが困難
- SRP(単一責任原則)違反、および変更耐性の低さ
設問 2:この構造を柔軟で保守性の高いものにするにはどうすべきか
以下の観点を含めて改善案を整理すること:
- 処理の流れをどこに切り出すべきか(呼び出し元 / 専用サービス / ラッパーなど)
- 呼び出し元は最小限の情報・操作のみで完結できるようにすべきか
- 将来の処理変更(追加/入替)に備えてどう構成すべきか
例:適用候補となるパターン
パターン名 | 主な目的と効果 |
---|---|
Facade | 一連の複雑な処理を 1 つの窓口としてまとめ、呼び出し元の負担を軽減 |
Proxy | 処理の実行前後に追加の制御(認証・ログなど)を加える |
Command | 各処理をコマンド化し、実行・記録・巻き戻しなどを抽象的に扱えるようにする |
Iterator | 一連の処理を順に実行する構造にし、処理の順序や構成の差し替えを柔軟にする |
発展課題(任意)
- 処理フローに「通知送信」「ポイント付与」などの追加処理を加える場合、どのように設計すれば既存コードを変更せずに拡張できるか?
- 同様の処理を複数箇所(Web/バッチなど)で再利用する場合、呼び出し方にどのような差異が生じるか?
提出フォーマット(レビュー・勉強会用)
- 問題点のリスト(3 点以上)
- 設計改善の方針と適用パターンの選定理由
- 改善後の構成概要(処理の隠蔽化、責務分担の分離など)