Menu
Coddy logo textTech

Go における Builder パターン

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

Builder Patternは、複雑なオブジェクトの構築をその表現から分離します。Functional Optionsパターンとは異なり、Builderは、オブジェクトを段階的に設定するメソッドを備えた専用のbuilder構造体を使用します。

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
}

各メソッドは builder ポインターを返すため、メソッドの chaining が可能になります。Build() メソッドは構築されたオブジェクトを完成させて返します。

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

Builder パターンは、オブジェクトに複数の設定手順が必要な場合や、特定の構築順序を強制したい場合に優れています。特に、不変オブジェクトを作成する場合や、構築プロセス自体に検証が必要な場合に役立ちます。

challenge icon

チャレンジ

簡単

Builder Patternを使ってメールメッセージ composer を作成しましょう!メールメッセージには、recipients、CC、subject、body、attachmentsなど、多くのオプションのコンポーネントがあるため、method chainingによる段階的な構築に最適です。

コードを2つのファイルに整理します。

  • email.go:Emailの構造体とそのbuilderをDefineします。

    次のfieldsを持つEmail structをCreateします。

    • from(string)
    • to(stringのslice)
    • cc(stringのslice)
    • subject(string)
    • body(string)

    構築中のEmailへのpointerをholdsするEmailBuilder structをCreateします。initializedされたEmail(toccにはemptyなslice)を持つ新しいbuilderを返すNewEmailBuilder()をImplementします。

    それぞれchainingのために*EmailBuilderを返す、次のbuilder methodsをAddします。

    • From(address string) - senderを設定します
    • To(address string) - to sliceにrecipientをAddします
    • CC(address string) - cc sliceにrecipientをAddします
    • Subject(subject string) - subject lineを設定します
    • Body(body string) - email bodyを設定します

    constructedされた*Emailを返すBuild() methodをAddします。

    Finally、Emailに、次のformatでstringを返すSummary() methodをAddします。

    From: [from]
    To: [comma-separated to addresses]
    CC: [comma-separated cc addresses or "none"]
    Subject: [subject]
    Body: [body]
  • main.go:builderを使用してemail messagesをBuildし、表示します。

    sender addressをReadし、続けてrecipientsのnumberと各recipient addressをReadします。次に、CC recipientsのnumberと各CC addressをReadします。Finally、subjectとbodyをReadします。

    EmailBuilderをmethod chainingとともに使用してemailをconstructし、そのsummaryをprintします。

次のinputsが提供されます。

  • Line 1:Sender address
  • Line 2:Recipientsのnumber
  • Following lines:各recipient address
  • Next line:CC recipientsのnumber
  • Following lines:各CC address
  • Next line:Subject
  • Final line:Body

たとえば、次のように入力された場合:

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.

builderを使うと、繰り返しTo()を呼び出すことで複数のrecipientsを1つずつAddできること、また、多くのオプションのfieldsがあっても、このpatternによって構築プロセスをcleanでreadableに保てることに注目してください。

自分で試してみよう

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腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ