design-patterns について
ソフトウェア構造の実装やレビューに取り組む Java 開発者向けの実践的なデザインパターン資料です。現代的な例で一般的なパターンの選び方を説明し、過剰な適用にも注意を促します。
主な機能
- 01パターン選択ガイド
- 02生成・構造・振る舞いパターン
- 03現代の Java と Spring の例
- 04アンチパターンへの注意
活用シーン
- 01オブジェクト生成のパターン選択
- 02硬直したコンポーネントのリファクタリング
- 03コードレビューでのパターン提案の検討
- 04互換性のないインターフェースの連携
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ decebals/ claude-code-java --skill design-patternsインストール後、Claude Code で/design-patternsを入力して読み込まれたか確認します。
同梱ファイルREADME.md
詳細
デザインパターンのスキル
Java の実用的なデザインパターンリファレンス。最新の例を紹介します。
使用する場面
- 特定のパターンの実装を求められたとき
- 拡張性や柔軟性のあるコンポーネントを設計するとき
- 硬直したコード構造をリファクタリングするとき
- コードレビューでパターンの利用を提案されたとき
クイックリファレンス:用途に応じたパターン
| 課題 | パターン |
|---|---|
| 複雑なオブジェクトの構築 | Builder |
| クラスを指定せずにオブジェクトを作成する | Factory |
| 複数のアルゴリズムを実行時に切り替える | Strategy |
| クラスを変更せずに動作を追加する | Decorator |
| 複数のオブジェクトに変更を通知する | Observer |
| インスタンスを1つだけにする | Singleton |
| 互換性のないインターフェースを変換する | Adapter |
| アルゴリズムの骨格を定義する | Template Method |
生成パターン
Builder
使用する場面: オブジェクトのパラメーターが多く、一部が任意の場合。
// ❌ Telescoping constructor antipattern
public class User {
public User(String name) { }
public User(String name, String email) { }
public User(String name, String email, int age) { }
public User(String name, String email, int age, String phone) { }
// ... explosion of constructors
}
// ✅ Builder pattern
public class User {
private final String name; // required
private final String email; // required
private final int age; // optional
private final String phone; // optional
private final String address; // optional
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static Builder builder(String name, String email) {
return new Builder(name, email);
}
public static class Builder {
// Required
private final String name;
private final String email;
// Optional with defaults
private int age = 0;
private String phone = "";
private String address = "";
private Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
// Usage
User user = User.builder("John", "[email protected]")
.age(30)
.phone("+1234567890")
.build();
Lombok を使う場合:
@Builder
@Getter
public class User {
private final String name;
private final String email;
@Builder.Default private int age = 0;
private String phone;
}
Factory Method
使用する場面: 正確なクラスを指定せずにオブジェクトを作成する必要がある場合。
// ✅ Factory Method pattern
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
public class PushNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Push: " + message);
}
}
// Factory
public class NotificationFactory {
public static Notification create(String type) {
return switch (type.toUpperCase()) {
case "EMAIL" -> new EmailNotification();
case "SMS" -> new SmsNotification();
case "PUSH" -> new PushNotification();
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
// Usage
Notification notification = NotificationFactory.create("EMAIL");
notification.send("Hello!");
Spring を使う場合(推奨):
public interface NotificationSender {
void send(String message);
String getType();
}
@Component
public class EmailSender implements NotificationSender {
@Override public void send(String message) { /* ... */ }
@Override public String getType() { return "EMAIL"; }
}
@Component
public class SmsSender implements NotificationSender {
@Override public void send(String message) { /* ... */ }
@Override public String getType() { return "SMS"; }
}
@Component
public class NotificationFactory {
private final Map<String, NotificationSender> senders;
public NotificationFactory(List<NotificationSender> senderList) {
this.senders = senderList.stream()
.collect(Collectors.toMap(
NotificationSender::getType,
Function.identity()
));
}
public NotificationSender getSender(String type) {
return Optional.ofNullable(senders.get(type))
.orElseThrow(() -> new IllegalArgumentException("Unknown: " + type));
}
}
Singleton
使用する場面: インスタンスが1つだけ必要な場合(使用は控えめにしてください!)。
// ✅ Modern singleton (enum-based, thread-safe)
public enum DatabaseConnection {
INSTANCE;
private Connection connection;
DatabaseConnection() {
// Initialize connection
}
public Connection getConnection() {
return connection;
}
}
// Usage
Connection conn = DatabaseConnection.INSTANCE.getConnection();
Spring を使う場合(推奨):
@Component // Default scope is singleton
public class DatabaseConnection {
// Spring manages single instance
}
警告: Singleton には問題が生じる可能性があります。
- テストが難しい(グローバル状態)
- 依存関係が隠れる
- 代わりに依存性注入を検討してください
振る舞いパターン
Strategy
使用する場面: 同じ操作に複数のアルゴリズムがあり、実行時に切り替える必要がある場合。
// ✅ Strategy pattern
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentStrategy {
private final String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " with card " + cardNumber);
}
}
public class PayPalPayment implements PaymentStrategy {
private final String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via PayPal: " + email);
}
}
public class CryptoPayment implements PaymentStrategy {
private final String walletAddress;
public CryptoPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " to wallet: " + walletAddress);
}
}
// Context
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(BigDecimal total) {
paymentStrategy.pay(total);
}
}
// Usage
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("4111-1111-1111-1111"));
cart.checkout(new BigDecimal("99.99"));
// Change strategy at runtime
cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
cart.checkout(new BigDecimal("49.99"));
Java 8 以降の場合(関数型):
// Strategy as functional interface
@FunctionalInterface
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// Usage with lambdas
PaymentStrategy creditCard = amount ->
System.out.println("Card payment: " + amount);
PaymentStrategy paypal = amount ->
System.out.println("PayPal payment: " + amount);
cart.setPaymentStrategy(creditCard);
Observer
使用する場面: あるオブジェクトの変更を別のオブジェクトに通知する必要がある場合。
// ✅ Observer pattern (modern Java)
public interface OrderObserver {
void onOrderPlaced(Order order);
}
public class OrderService {
private final List<OrderObserver> observers = new ArrayList<>();
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
public void removeObserver(OrderObserver observer) {
observers.remove(observer);
}
public void placeOrder(Order order) {
// Process order
saveOrder(order);
// Notify all observers
observers.forEach(observer -> observer.onOrderPlaced(order));
}
}
// Observers
public class InventoryService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
// Reduce inventory
order.getItems().forEach(item ->
reduceStock(item.getProductId(), item.getQuantity())
);
}
}
public class EmailNotificationService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
sendConfirmationEmail(order.getCustomerEmail(), order);
}
}
public class AnalyticsService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
trackOrderEvent(order);
}
}
// Setup
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());
Spring Events を使う場合(推奨):
// Event
public record OrderPlacedEvent(Order order) {}
// Publisher
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public void placeOrder(Order order) {
saveOrder(order);
eventPublisher.publishEvent(new OrderPlacedEvent(order));
}
}
// Listeners (observers)
@Component
public class InventoryListener {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Reduce inventory
}
}
@Component
public class EmailListener {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Send email
}
@EventListener
@Async // Async processing
public void handleOrderPlacedAsync(OrderPlacedEvent event) {
// Send email asynchronously
}
}
Template Method
使用する場面: アルゴリズムの骨格を定義し、サブクラスで各ステップを実装する場合。
// ✅ Template Method pattern
public abstract class DataProcessor {
// Template method - defines the algorithm
public final void process() {
readData();
processData();
writeData();
if (shouldNotify()) {
notifyCompletion();
}
}
// Steps to be implemented by subclasses
protected abstract void readData();
protected abstract void processData();
protected abstract void writeData();
// Hook - optional override
protected boolean shouldNotify() {
return true;
}
protected void notifyCompletion() {
System.out.println("Processing completed!");
}
}
public class CsvDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading CSV file...");
}
@Override
protected void processData() {
System.out.println("Processing CSV data...");
}
@Override
protected void writeData() {
System.out.println("Writing to database...");
}
}
public class ApiDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Fetching from API...");
}
@Override
protected void processData() {
System.out.println("Transforming API response...");
}
@Override
protected void writeData() {
System.out.println("Writing to cache...");
}
@Override
protected boolean shouldNotify() {
return false; // Override hook
}
}
// Usage
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process();
DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process();
構造パターン
Decorator
使用する場面: 既存のクラスを変更せずに、動的に動作を追加する場合。
// ✅ Decorator pattern
public interface Coffee {
String getDescription();
BigDecimal getCost();
}
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Coffee";
}
@Override
public BigDecimal getCost() {
return new BigDecimal("2.00");
}
}
// Base decorator
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public BigDecimal getCost() {
return coffee.getCost();
}
}
// Concrete decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.50"));
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.20"));
}
}
public class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Whipped Cream";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.70"));
}
}
// Usage - compose decorators
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription()); // Coffee, Milk, Sugar, Whipped Cream
System.out.println(coffee.getCost()); // 3.40
Java の I/O では Decorator を使用します:
// Classic example from Java
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt")
)
);
Adapter
使用する場面: 互換性のないインターフェースを連携させる場合。
// ✅ Adapter pattern
// Existing interface our code uses
public interface MediaPlayer {
void play(String filename);
}
// Legacy/third-party interface
public class LegacyAudioPlayer {
public void playMp3(String filename) {
System.out.println("Playing MP3: " + filename);
}
}
public class AdvancedVideoPlayer {
public void playMp4(String filename) {
System.out.println("Playing MP4: " + filename);
}
public void playAvi(String filename) {
System.out.println("Playing AVI: " + filename);
}
}
// Adapters
public class Mp3PlayerAdapter implements MediaPlayer {
private final LegacyAudioPlayer legacyPlayer = new LegacyAudioPlayer();
@Override
public void play(String filename) {
legacyPlayer.playMp3(filename);
}
}
public class VideoPlayerAdapter implements MediaPlayer {
private final AdvancedVideoPlayer videoPlayer = new AdvancedVideoPlayer();
@Override
public void play(String filename) {
if (filename.endsWith(".mp4")) {
videoPlayer.playMp4(filename);
} else if (filename.endsWith(".avi")) {
videoPlayer.playAvi(filename);
}
}
}
// Usage
MediaPlayer mp3Player = new Mp3PlayerAdapter();
mp3Player.play("song.mp3");
MediaPlayer videoPlayer = new VideoPlayerAdapter();
videoPlayer.play("movie.mp4");
パターン選択ガイド
| 状況 | 検討するパターン |
|---|---|
| オブジェクトの生成が複雑 | Builder、Factory |
| 機能を動的に追加する必要がある | Decorator |
| アルゴリズムの実装が複数ある | Strategy |
| 状態の変化に対応する | Observer |
| レガシーコードと連携する | Adapter |
| 共通のアルゴリズムで、ステップが異なる | Template Method |
| インスタンスを1つだけにする必要がある | Singleton(使用は控えめに) |
避けるべきアンチパターン
| アンチパターン | 問題 | より良いアプローチ |
|---|---|---|
| Singleton の乱用 | グローバル状態、テストが難しい | 依存性注入 |
| どこでも Factory を使う | 過剰な設計 | 型が分かっているならシンプルに new を使う |
| Decorator の深いチェーン | デバッグが難しい | チェーンを短く保ち、コンポジションを検討する |
| イベントが多すぎる Observer | 通知がスパゲッティ化する | イベントバス、明確なイベント階層 |
関連スキル
solid-principles- パターンの実装に役立つ設計原則clean-code- コードレベルのベストプラクティスspring-boot-patterns- Spring 固有の実装
コメント
Markdown 対応