Interactive

Builder Pattern

プロンプトに「Builder」を含めることで、ClaudeCodeは複雑なオブジェクトを段階的に構築し、読みやすいメソッドチェーンを提供する実装を生成できます。弊社では、SQLクエリの構築、設定オブジェクトの生成、APIリクエストの組み立てで複数のオプションを組み合わせたい場合にBuilderパターンを使用します。

弊社の推奨ルール

  1. イミュータブル設計 - 各メソッドで新しいインスタンスを返す
  2. メソッドチェーン対応 - 流暢なインターフェースの提供
  3. 型安全性の確保 - 必須パラメータの段階的な型チェック
  4. 直感的な命名 - withやsetなどの一貫したプレフィックス
  5. コンストラクタの簡素化 - 複雑な引数リストを避ける

ClaudeCodeでの利用

既存クラスの変換

複雑なコンストラクタをBuilderパターンに置き換える場合

「このクラスをBuilderパターンに書き換えて。メソッドチェーンできるようにして」

SQLクエリの構築

動的なクエリを段階的に組み立てる場合

「QueryBuilderを実装して。WHERE、ORDER BY、LIMITを段階的に設定できるように」

設定オブジェクトの生成

多数の設定項目を持つオブジェクトを構築する場合

「ConfigurationBuilderを作成して。各設定項目を流暢に設定できるBuilderパターンで」

複雑な設定ではなくBuilderパターンを使う

複数のオプションを持つオブジェクトはBuilderパターンで段階的に構築します。コンストラクタの引数が多くなることによる可読性の低下と、引数の順序ミスによるバグを防げます。

// ❌ 避ける:複雑なコンストラクタ
class HttpRequest {
  constructor(
    url: string,
    method: string,
    headers: Record<string, string>,
    body?: string,
    timeout?: number,
    retries?: number
  ) {
    // 引数が多くて使いづらい、順序を間違えやすい
  }
}

// 使用時に可読性が低い
const request = new HttpRequest(
  'https://api.example.com/users', 
  'POST', 
  {'Content-Type': 'application/json'},
  '{"name":"test"}',
  5000,
  3
);

コンストラクタに多数の引数があると、どの値がどの設定かわからず、引数の順序を間違えやすくなります。Builderパターンで段階的に構築します。


// ✅ 推奨:Builderパターン
class HttpRequestBuilder {
  constructor(
    private readonly url: string = '',
    private readonly method: string = 'GET',
    private readonly headers: Record<string, string> = {},
    private readonly body?: string,
    private readonly timeout: number = 30000,
    private readonly retries: number = 0
  ) {
    Object.freeze(this);
  }
  
  static create(url: string): HttpRequestBuilder {
    return new HttpRequestBuilder(url);
  }

Builderクラスでは各設定項目を個別のメソッドで設定し、メソッドチェーンを可能にします。静的ファクトリーメソッドで必須項目を最初に設定します。

  
  withMethod(method: string): HttpRequestBuilder {
    return new HttpRequestBuilder(
      this.url, method, this.headers, 
      this.body, this.timeout, this.retries
    );
  }
  
  withHeader(key: string, value: string): HttpRequestBuilder {
    return new HttpRequestBuilder(
      this.url, this.method, 
      { ...this.headers, [key]: value },
      this.body, this.timeout, this.retries
    );
  }
  
  withBody(body: string): HttpRequestBuilder {
    return new HttpRequestBuilder(
      this.url, this.method, this.headers, 
      body, this.timeout, this.retries
    );
  }

各withメソッドが新しいインスタンスを返すことで、イミュータブルな設計を実現します。設定項目ごとに分けることで、コードの意図が明確になります。

  withTimeout(timeout: number): HttpRequestBuilder {
    return new HttpRequestBuilder(
      this.url, this.method, this.headers, 
      this.body, timeout, this.retries
    );
  }
  
  withRetries(retries: number): HttpRequestBuilder {
    return new HttpRequestBuilder(
      this.url, this.method, this.headers, 
      this.body, this.timeout, retries
    );
  }
  
  build(): HttpRequest {
    return new HttpRequest(
      this.url, this.method, this.headers, 
      this.body, this.timeout, this.retries
    );
  }
}

buildメソッドは構築フェーズの終了を示し、最終的なオブジェクトを生成します。設定内容を確認してから実際のインスタンスを作成します。

// 使用例:メソッドチェーンで直感的に構築
const request = HttpRequestBuilder
  .create('https://api.example.com/users')
  .withMethod('POST')
  .withHeader('Content-Type', 'application/json')
  .withHeader('Authorization', 'Bearer token')
  .withBody('{"name":"test"}')
  .withTimeout(5000)
  .withRetries(3)
  .build();

このパターンにより、必要な設定のみを指定でき、設定の組み合わせが直感的に理解できます。

段階的な型チェックではなく幽霊型Builderを使う

幽霊型(Phantom Type)を使用して、必須フィールドの設定状態を型レベルで追跡します。これにより、コンパイル時に必須項目の設定漏れを防げます。

// 設定状態を表す幽霊型
type Empty = { _brand: 'empty' };
type WithUrl = { _brand: 'withUrl' };
type WithMethod = { _brand: 'withMethod' };
type Complete = WithUrl & WithMethod;

class TypeSafeBuilder<State = Empty> {
  private readonly _phantom!: State;
  
  constructor(
    private readonly url?: string,
    private readonly method?: string,
    private readonly headers: Record<string, string> = {}
  ) {
    Object.freeze(this);
  }

各メソッドで型パラメータを更新し、設定状態を追跡します。

  withUrl(url: string): TypeSafeBuilder<State | WithUrl> {
    return new TypeSafeBuilder(
      url, 
      this.method, 
      this.headers
    ) as TypeSafeBuilder<State | WithUrl>;
  }
  
  withMethod(method: string): TypeSafeBuilder<State | WithMethod> {
    return new TypeSafeBuilder(
      this.url, 
      method, 
      this.headers
    ) as TypeSafeBuilder<State | WithMethod>;
  }
  
  withHeader(key: string, value: string): TypeSafeBuilder<State> {
    return new TypeSafeBuilder(
      this.url,
      this.method,
      { ...this.headers, [key]: value }
    ) as TypeSafeBuilder<State>;
  }

buildメソッドは、必須フィールドが全て設定された時のみ利用可能になります。

  // Completeの時のみbuildメソッドが使える
  build(this: TypeSafeBuilder<Complete>): HttpRequest {
    return new HttpRequest(
      this.url!, 
      this.method!,
      this.headers
    );
  }
}

// 使用例
const builder = new TypeSafeBuilder();

// ❌ エラー:buildメソッドがまだ使えない
// builder.build(); 

// ✅ 必須項目を設定するとbuildが使える
const request = builder
  .withUrl('https://api.example.com')
  .withMethod('GET')
  .withHeader('Authorization', 'Bearer token')
  .build(); // 型チェックが通る

幽霊型により、実行時エラーではなくコンパイル時にエラーを検出できます。

よくある問題

Builderパターンでのミュータブルな実装

Builderで既存インスタンスを変更してしまう問題です。イミュータブルに実装し、各メソッドで新しいインスタンスを返すことで解決できます。

// ❌ ミュータブルな実装
class BadBuilder {
  private config: any = {};
  
  set(key: string, value: any) {
    this.config[key] = value; // 副作用あり
    return this;
  }
}

// ✅ イミュータブルな実装
class GoodBuilder {
  constructor(private readonly config: Record<string, any> = {}) {
    Object.freeze(this);
  }
  
  set(key: string, value: any): GoodBuilder {
    return new GoodBuilder({ ...this.config, [key]: value });
  }
}

必須パラメータのチェック

Builderパターンで必須パラメータが設定されていない問題です。TypeScriptの型システムで段階的にチェックすることで解決できます。

// ✅ 型安全なBuilder(段階的な型チェック)
type RequiredFields = 'url' | 'method';
type OptionalFields = 'headers' | 'body' | 'timeout';

class TypedRequestBuilder<T extends RequiredFields = never> {
  constructor(private readonly config: Partial<HttpRequestConfig> = {}) {}
  
  withUrl(url: string): TypedRequestBuilder<T | 'url'> {
    return new TypedRequestBuilder({ ...this.config, url });
  }
  
  withMethod(method: string): TypedRequestBuilder<T | 'method'> {
    return new TypedRequestBuilder({ ...this.config, method });
  }
  
  // 必須フィールドが全て設定された時のみbuild可能
  build(this: TypedRequestBuilder<RequiredFields>): HttpRequest {
    return new HttpRequest(this.config as Required<HttpRequestConfig>);
  }
}

パフォーマンスの懸念

大量のインスタンス作成によるパフォーマンスの問題です。Object.freezeの使用を最適化することで解決できます。

// ✅ パフォーマンス最適化
class OptimizedBuilder {
  private readonly _frozen: boolean = false;
  
  constructor(private readonly config: Config = {}) {
    // 開発時のみfreeze
    if (process.env.NODE_ENV === 'development') {
      Object.freeze(this);
      this._frozen = true;
    }
  }
}

Q: Fluent APIとの違いは?

A: Builderは最終的にbuild()でオブジェクトを生成、Fluent APIは直接オブジェクトを操作します。

Q: いつBuilderパターンを使うべき?

A: コンストラクタ引数が4個以上、またはオプション設定が複雑な場合に使用します。