メソッドチェーン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 81/107。
メソッドチェーンは、メソッドがレシーバを返すことで、1つのステートメント内で複数のメソッド呼び出しを連結できるようにする手法です。これはすでにBuilderパターンで目にしていると思いますが、多くの文脈で役立つ独立した概念です。
ポイントは、各メソッドから構造体へのポインタを返すことです:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}これで、これらの呼び出しを流暢に連結させることができます:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)これは、個別のステートメントと比較して、ほとんど自然言語のように読めます。メソッドチェーンは、他の値を返す必要がなく、操作がオブジェクトの設定や変更を行う場合に最も効果的です。クエリビルダー、設定API、テストアサーションライブラリなどでよく使用されます。
チェーンが正しく機能するためには、各メソッドがポインタレシーバを使用し、*StructType を返す必要があることに注意してください。
チャレンジ
簡単メソッドチェーンを使用して、属性とコンテンツを持つHTMLタグを構築する、流暢なHTML要素ビルダーを作成しましょう!これはメソッドチェーンの実用的な応用例です。HTML要素を構築する際に、まるで自然言語のように読めるAPIを作成します。
コードは2つのファイルに分けて構成します:
element.go: チェーン可能なメソッドを持つHTML要素ビルダーを定義します。タグ名、属性のマップ(文字列から文字列)、および内部コンテンツを格納する
Element構造体を作成します。それぞれが*Elementを返す、以下のチェーン可能なメソッドを構築してください:NewElement(tag string)- 指定されたタグ名と初期化された(空の)属性マップを持つ新しい Element を作成するコンストラクタAttr(key, value string)- 要素に属性を追加するID(id string)- "id" 属性を設定する便利なメソッドClass(class string)- "class" 属性を設定する便利なメソッドContent(text string)- 要素の内部コンテンツを設定する
HTML文字列を返す
Render()メソッドを追加します。属性はキーのアルファベット順に表示される必要があります。形式は以下の通りです:<tag attr1="value1" attr2="value2">content</tag>属性がない場合は、単に
<tag>content</tag>とレンダリングします。コンテンツがない場合は、<tag attr="value"></tag>とレンダリングします。main.go: メソッドチェーンを使用してHTML要素を構築します。タグ名を読み取り、次に実行する操作の数を読み取ります。各操作は以下のいずれかです:
idとそれに続く ID の値classとそれに続くクラスの値attrとそれに続く属性名および値(2行)contentとそれに続くコンテンツのテキスト
すべての操作を流暢にチェーンさせ、レンダリングされたHTMLを出力します。
以下の入力が提供されます:
- 1行目:タグ名
- 2行目:操作の数
- 続く行:操作のタイプと値
例えば、以下の場合:
div
3
id
container
class
main-content
content
Hello World出力は以下のようになります:
<div class="main-content" id="container">Hello World</div>また、以下の場合:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click here出力は以下のようになります:
<a href="https://example.com" target="_blank">Click here</a>また、以下の場合:
input
2
attr
type
text
attr
placeholder
Enter name出力は以下のようになります:
<input placeholder="Enter name" type="text"></input>また、以下の場合:
p
1
content
Simple paragraph出力は以下のようになります:
<p>Simple paragraph</p>メソッドチェーンによって、読みやすい方法でステップバイステップで要素を構築できることに注目してください。各メソッドは要素を変更してそれを返すため、次のメソッド呼び出しを直接つなげることができます。
チートシート
メソッドチェーンは、メソッドがレシーバ(通常は *StructType)を返すことで、複数のメソッド呼び出しを単一のステートメントで連結できるようにする手法です。
各メソッドはポインタレシーバを使用し、*StructType を返す必要があります:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}メソッド呼び出しを流暢に連結します:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)メソッドチェーンは、他の値を返す必要がなく、オブジェクトの設定や変更を行う操作に最適です。クエリビルダー、設定API、テストアサーションライブラリなどで一般的によく使用されます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// タグ名を読み込む
scanner.Scan()
tagName := scanner.Text()
// 操作の数を読み込む
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// 要素を作成する
element := NewElement(tagName)
// 各操作を処理する
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: 操作のタイプに基づいて、適切なメソッドをチェーンさせる
// 操作は "id"、"class"、"attr"、または "content" のいずれかです
// - "id" の場合: 次の行を ID 値として読み込む
// - "class" の場合: 次の行をクラス値として読み込む
// - "attr" の場合: 次の2行(属性名、その後の値)を読み込む
// - "content" の場合: 次の行をコンテンツテキストとして読み込む
// メソッドチェーンを使用して、要素を流暢に構築する
_ = operation // 実装時にこの行を削除する
}
// レンダリングされた HTML を出力する
fmt.Println(element.Render())
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー