Menu
Coddy logo textTech

Go における Builder パターン

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 80/107。

Builder パターンは、複雑なオブジェクトの構築とその表現を分離します。Functional Options パターンとは異なり、Builder はオブジェクトをステップバイステップで構成するメソッドを備えた専用の builder 構造体(struct)を使用します。

HTTPリクエストのビルダーを実装する方法を以下に示します。

type Request struct {
    method  string
    url     string
    headers map[string]string
    body    string
}

type RequestBuilder struct {
    request *Request
}

func NewRequestBuilder() *RequestBuilder {
    return &RequestBuilder{
        request: &Request{
            headers: make(map[string]string),
        },
    }
}

func (b *RequestBuilder) Method(m string) *RequestBuilder {
    b.request.method = m
    return b
}

func (b *RequestBuilder) URL(u string) *RequestBuilder {
    b.request.url = u
    return b
}

func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
    b.request.headers[key] = value
    return b
}

func (b *RequestBuilder) Build() *Request {
    return b.request
}

各メソッドはビルダーのポインタを返し、メソッドチェーンを可能にします。Build() メソッドは、構築されたオブジェクトを確定して返します:

req := NewRequestBuilder().
    Method("POST").
    URL("https://api.example.com").
    Header("Content-Type", "application/json").
    Build()

Builderパターンは、オブジェクトが複数の設定ステップを必要とする場合や、特定の構築順序を強制したい場合に非常に適しています。これは、イミュータブルなオブジェクトを作成する場合や、構築プロセス自体にバリデーションが必要な場合に特に有用です。

challenge icon

チャレンジ

簡単

Builderパターンを使用して、電子メールメッセージの作成ツールを構築しましょう!電子メールメッセージには、宛先、CC、件名、本文、添付ファイルなど、多くのオプションコンポーネントが含まれるため、メソッドチェーンを使用したステップバイステップの構築に最適な候補です。

コードを2つのファイルに分けて構成します:

  • email.go: 電子メールの構造体とそのビルダーを定義します。

    以下のフィールドを持つ Email 構造体を作成します:

    • from (string)
    • to (文字列のスライス)
    • cc (文字列のスライス)
    • subject (string)
    • body (string)

    構築中のEmailへのポインタを保持する EmailBuilder 構造体を作成します。初期化されたEmail(tocc は空のスライス)を持つ新しいビルダーを返す NewEmailBuilder() を実装します。

    メソッドチェーンのために、それぞれ *EmailBuilder を返す以下のビルダーメソッドを追加します:

    • From(address string) - 送信者を設定します
    • To(address string) - to スライスに宛先を追加します
    • CC(address string) - cc スライスに宛先を追加します
    • Subject(subject string) - 件名を設定します
    • Body(body string) - メールの本文を設定します

    構築された *Email を返す Build() メソッドを追加します。

    最後に、Emailに以下の形式の文字列を返す Summary() メソッドを追加します:

    From: [from]
    To: [comma-separated to addresses]
    CC: [comma-separated cc addresses or "none"]
    Subject: [subject]
    Body: [body]
  • main.go: ビルダーを使用して電子メールメッセージを構築し、表示します。

    送信者アドレスを読み取り、次に宛先の数と各宛先アドレスを読み取ります。次に、CCの宛先数と各CCアドレスを読み取ります。最後に、件名と本文を読み取ります。

    メソッドチェーンで EmailBuilder を使用して電子メールを構築し、そのサマリーを出力します。

以下の入力が提供されます:

  • 1行目: 送信者アドレス
  • 2行目: 宛先の数
  • 続く行: 各宛先アドレス
  • 次の行: CCの宛先数
  • 続く行: 各CCアドレス
  • 次の行: 件名
  • 最終行: 本文

例えば、以下が与えられた場合:

alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.

出力は以下のようになります:

From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.

また、以下が与えられた場合:

support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.

出力は以下のようになります:

From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.

ビルダーを使用することで、To() メソッドを繰り返し呼び出すことで複数の宛先を1つずつ追加できること、また、多くのオプションフィールドがある場合でも、このパターンによって構築プロセスがクリーンで読みやすくなることに注目してください。

チートシート

Builder パターンは、複雑なオブジェクトの構築をその表現から分離し、オブジェクトをステップバイステップで設定するメソッドを持つ専用のビルダー構造体を使用します。

基本的な構造:

type Request struct {
    method  string
    url     string
    headers map[string]string
    body    string
}

type RequestBuilder struct {
    request *Request
}

func NewRequestBuilder() *RequestBuilder {
    return &RequestBuilder{
        request: &Request{
            headers: make(map[string]string),
        },
    }
}

ビルダーメソッドは、メソッドチェーンを可能にするためにビルダーのポインタを返します:

func (b *RequestBuilder) Method(m string) *RequestBuilder {
    b.request.method = m
    return b
}

func (b *RequestBuilder) URL(u string) *RequestBuilder {
    b.request.url = u
    return b
}

func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
    b.request.headers[key] = value
    return b
}

Build() メソッドは、構築されたオブジェクトを完成させて返します:

func (b *RequestBuilder) Build() *Request {
    return b.request
}

メソッドチェーンを使用した使用例:

req := NewRequestBuilder().
    Method("POST").
    URL("https://api.example.com").
    Header("Content-Type", "application/json").
    Build()

Builder パターンは、オブジェクトに複数の設定ステップが必要な場合、特定の構築順序を強制する場合、不変(immutable)オブジェクトを作成する場合、または構築プロセスにバリデーションが必要な場合に有用です。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 送信者のアドレスを読み取る
	from, _ := reader.ReadString('\n')
	from = strings.TrimSpace(from)

	// 宛先の数を読み取る
	toCountStr, _ := reader.ReadString('\n')
	toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))

	// 各宛先のアドレスを読み取る
	toAddresses := make([]string, toCount)
	for i := 0; i < toCount; i++ {
		addr, _ := reader.ReadString('\n')
		toAddresses[i] = strings.TrimSpace(addr)
	}

	// CCの宛先数を読み取る
	ccCountStr, _ := reader.ReadString('\n')
	ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))

	// 各CCのアドレスを読み取る
	ccAddresses := make([]string, ccCount)
	for i := 0; i < ccCount; i++ {
		addr, _ := reader.ReadString('\n')
		ccAddresses[i] = strings.TrimSpace(addr)
	}

	// 件名を読み取る
	subject, _ := reader.ReadString('\n')
	subject = strings.TrimSpace(subject)

	// 本文を読み取る
	body, _ := reader.ReadString('\n')
	body = strings.TrimSpace(body)

	// TODO: メソッドチェーンを使用して EmailBuilder でメールを構築する
	// NewEmailBuilder() から始め、From()、To()、CC()、Subject()、Body() をチェーンさせる
	// 最後に Build() を呼び出して Email を取得する

	// TODO: Summary() メソッドを使用してメールの概要を出力する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン