Communityコーディング&開発github.com

viccom/design-patterns

Java サンプル付きの代表的なデザインパターン(Factory、Builder、Strategy、Observer、Decorator など)。ユーザーが「パターンを実装」「ファクトリを使用」「ストラテジーパターン」と依頼した場合や、拡張可能なコンポーネント設計時に使用します。

design-patterns とは?

このスキルは、AIエージェント(Claude Code、Cursor、Codex 等)向けに、Java のデザインパターンを実践的に解説したリファレンスです。Factory、Builder、Strategy、Observer、Decorator、Singleton、Adapter、Template Method といった主要な GoF パターンを、問題状況(「複雑なオブジェクト構築」「実行時のアルゴリズム切り替え」「動的な振る舞い追加」など)と照らし合わせて即座に選択・実装できるよう支援します。各パターンには「アンチパターンとの対比」「Lombok や Spring による簡潔な実装」「Java 8+ の関数型インターフェースを用いたモダンな記法」など、実務で即活用できるコード例が豊富に含まれています。AIエージェントは、ユーザーが「ファクトリパターンを使って」や「オブザーバーで実装して」と指示した際に、このスキルを参照して適切なパターンを選び、推奨される Spring のイベント機構や @Builder などモダンな手法を優先したコードを生成できます。結果として、コードの柔軟性・保守性を高め、リファクタリングやコードレビュー時のパターン適用を効率化します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/design-patterns

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

デザインパターンスキル

Java の実践的なデザインパターンリファレンス(最新のサンプル付き)。

使用タイミング

  • ユーザーが特定のパターンの実装を依頼したとき
  • 拡張可能/柔軟なコンポーネントを設計するとき
  • 硬直化したコード構造をリファクタリングするとき
  • コードレビューでパターンの使用が提案されたとき

クイックリファレンス:いつどのパターンを使うか

問題パターン
複雑なオブジェクトの構築Builder
クラスを指定せずにオブジェクトを作成Factory
複数のアルゴリズム、実行時に切り替えStrategy
クラスを変更せずに振る舞いを追加Decorator
複数のオブジェクトに変更を通知Observer
単一インスタンスを保証Singleton
互換性のないインターフェースを変換Adapter
アルゴリズムの骨格を定義Template Method

生成に関するパターン

Builder

使用するケース: オブジェクトに多くのパラメーターがあり、一部はオプションの場合。

// ❌ テレスコーピングコンストラクタ(アンチパターン)
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) { }
    // ... コンストラクタの爆発的な増加
}

// ✅ Builderパターン
public class User {
    private final String name;      // 必須
    private final String email;     // 必須
    private final int age;          // オプション
    private final String phone;     // オプション
    private final String address;   // オプション

    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 {
        // 必須
        private final String name;
        private final String email;
        // オプション(デフォルト値設定)
        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);
        }
    }
}

// 使用例
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パターン
public interface Notification {
    void send(String message);
}

public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("メール: " + 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("プッシュ: " + message);
    }
}

// ファクトリ
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);
        };
    }
}

// 使用例
Notification notification = NotificationFactory.create("EMAIL");
notification.send("こんにちは!");

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("不明なタイプ: " + type));
    }
}

Singleton

使用するケース: インスタンスが正確に1つだけ必要な場合(使用は控えめに!)。

// ✅ モダンなSingleton(enumベース、スレッドセーフ)
public enum DatabaseConnection {
    INSTANCE;

    private Connection connection;

    DatabaseConnection() {
        // 接続を初期化
    }

    public Connection getConnection() {
        return connection;
    }
}

// 使用例
Connection conn = DatabaseConnection.INSTANCE.getConnection();

Springを使う場合(推奨):

@Component  // デフォルトスコープはシングルトン
public class DatabaseConnection {
    // Springが単一インスタンスを管理
}

警告: Singletonには問題点があります:

  • テストが難しい(グローバルステート)
  • 依存関係が隠蔽される
  • 代わりに依存性注入を検討しましょう

振る舞いに関するパターン

Strategy

使用するケース: 同じ操作に対して複数のアルゴリズムがあり、実行時に切り替える必要がある場合。

// ✅ Strategyパターン
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("カード " + cardNumber + " で " + amount + " を支払いました");
    }
}

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("PayPal (" + email + ") で " + amount + " を支払いました");
    }
}

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("ウォレット " + walletAddress + " に " + amount + " を支払いました");
    }
}

// コンテキスト
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(BigDecimal total) {
        paymentStrategy.pay(total);
    }
}

// 使用例
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("4111-1111-1111-1111"));
cart.checkout(new BigDecimal("99.99"));

// 実行時に戦略を変更
cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
cart.checkout(new BigDecimal("49.99"));

Java 8+(関数型)の場合:

// 関数型インターフェースとしての戦略
@FunctionalInterface
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// ラムダ式を使った使用例
PaymentStrategy creditCard = amount ->
    System.out.println("カード支払い: " + amount);

PaymentStrategy paypal = amount ->
    System.out.println("PayPal支払い: " + amount);

cart.setPaymentStrategy(creditCard);

Observer

使用するケース: あるオブジェクトの変更を他のオブジェクトに通知する必要がある場合。

// ✅ Observerパターン(モダン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) {
        // 注文処理
        saveOrder(order);

        // すべてのオブザーバーに通知
        observers.forEach(observer -> observer.onOrderPlaced(order));
    }
}

// オブザーバー
public class InventoryService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        // 在庫を減らす
        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);
    }
}

// 設定
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());

Spring Eventsを使う場合(推奨):

// イベント
public record OrderPlacedEvent(Order order) {}

// パブリッシャー
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;

    public void placeOrder(Order order) {
        saveOrder(order);
        eventPublisher.publishEvent(new OrderPlacedEvent(order));
    }
}

// リスナー(オブザーバー)
@Component
public class InventoryListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // 在庫を減らす
    }
}

@Component
public class EmailListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // メールを送信
    }

    @EventListener
    @Async  // 非同期処理
    public void handleOrderPlacedAsync(OrderPlacedEvent event) {
        // 非同期でメールを送信
    }
}

Template Method

使用するケース: アルゴリズムの骨格を定義し、サブクラスにステップの実装を任せる場合。

// ✅ Template Methodパターン
public abstract class DataProcessor {

    // テンプレートメソッド - アルゴリズムを定義
    public final void process() {
        readData();
        processData();
        writeData();
        if (shouldNotify()) {
            notifyCompletion();
        }
    }

    // サブクラスによって実装されるステップ
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeData();

    // フック - 必要に応じてオーバーライド
    protected boolean shouldNotify() {
        return true;
    }

    protected void notifyCompletion() {
        System.out.println("処理が完了しました!");
    }
}

public class CsvDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("CSVファイルを読み込み中...");
    }

    @Override
    protected void processData() {
        System.out.println("CSVデータを処理中...");
    }

    @Override
    protected void writeData() {
        System.out.println("データベースに書き込み中...");
    }
}

public class ApiDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("APIから取得中...");
    }

    @Override
    protected void processData() {
        System.out.println("APIレスポンスを変換中...");
    }

    @Override
    protected void writeData() {
        System.out.println("キャッシュに書き込み中...");
    }

    @Override
    protected boolean shouldNotify() {
        return false;  // フックをオーバーライド
    }
}

// 使用例
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process();

DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process();

構造に関するパターン

Decorator

使用するケース: 既存のクラスを変更せずに動的に振る舞いを追加する場合。

// ✅ Decoratorパターン
public interface Coffee {
    String getDescription();
    BigDecimal getCost();
}

public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "コーヒー";
    }

    @Override
    public BigDecimal getCost() {
        return new BigDecimal("2.00");
    }
}

// 基本デコレーター
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();
    }
}

// 具象デコレーター
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + "、ミルク";
    }

    @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() + "、砂糖";
    }

    @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() + "、ホイップクリーム";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.70"));
    }
}

// 使用例
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);

System.out.println(coffee.getDescription() + " = $" + coffee.getCost());
// 出力: コーヒー、ミルク、砂糖、ホイップクリーム = $3.40

補足

アダプター

使用するケース: 互換性のないインターフェースを連携させる場合。

// サードパーティのクラス(変更不可)
public class LegacyLibrary {
    public String fetchOldData() {
        return "古いデータ";
    }
}

// 期待されるインターフェース
public interface DataProvider {
    String getData();
}

// ✅ Adapterパターン
public class LegacyAdapter implements DataProvider {
    private final LegacyLibrary legacy;

    public LegacyAdapter(LegacyLibrary legacy) {
        this.legacy = legacy;
    }

    @Override
    public String getData() {
        return legacy.fetchOldData();
    }
}

// 使用例
DataProvider provider = new LegacyAdapter(new LegacyLibrary());
String data = provider.getData();

重要な注意点:

  • パターンは硬直したルールではなく、ガイドラインとして使用する
  • 単純な解決策で十分な場合にパターンを過剰に適用しない
  • パターンがもたらすトレードオフ(複雑さ、間接性)を常に考慮する
  • Spring Framework は多くのパターン(DI、Proxy、Template Method)を内部的に実装しているため、手動実装よりもSpringの機能を優先する

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル