契約としてのインターフェース
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 27/107。
インターフェースを契約として考えることは、より良いGoプログラムを設計するのに役立ちます。関数がインターフェース型を受け入れるとき、それは「この契約を満たしている限り、どの具体的な型を渡されても構わない」と言っているのです。
この契約ベースの考え方により、特定の型ではなく振る舞いに焦点を当てた関数を記述できるようになります。さまざまなソースからのデータを処理する必要がある関数を考えてみましょう。
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}ProcessData 関数は、データベース接続、API クライアント、またはファイルリーダーのいずれを受け取るかを知る必要も、気にする必要もありません。入力が FetchData() を実行できることだけを要求します。このコントラクト(規約)を遵守する型であれば、どのようなものでも動作します。
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // 動作します!
ProcessData(api) // こちらも動作します!
}この契約によるアプローチは、コードのテストのしやすさも向上させます。テスト時には、実際のデータベースやネットワーク接続を必要とせずに、インターフェースを満たすモック型を作成できます。契約が満たされているため、テスト対象の関数はその違いを認識しません。
チャレンジ
簡単インターフェースがどのように契約として機能するかを示す通知システムを構築しましょう。通知を送信するとはどういうことかを定義する Notifier インターフェースを作成し、この契約を遵守する複数の通知チャネルを実装します。
コードを2つのファイルに分けて整理します:
notifiers.go:Notify(message string) stringという単一のメソッドを持つNotifierインターフェースを定義します。次に、この契約を満たす3つの異なる構造体を作成します:Prefixフィールドを持つConsoleNotifierFilenameフィールドを持つFileNotifierURLフィールドを持つWebhookNotifier
Notifyメソッドを実装する必要があります。main.go:Notifier値のスライスとメッセージ文字列を受け取るBroadcastAlertという関数を作成します。この関数は、各通知機能でNotifyを呼び出し、その結果を出力する必要があります。入力から設定を読み取り、3つの通知タイプすべてを作成し、それらすべてを通じてアラートをブロードキャストします。
以下の入力が提供されます:
- 1行目:コンソールのプレフィックス
- 2行目:ファイル名
- 3行目:WebhookのURL
- 4行目:アラートメッセージ
Notify メソッドは、以下の形式の文字列を返す必要があります:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
例えば、ALERT:、log.txt、https://hooks.example.com、Server down! が与えられた場合、出力は以下のようになります:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!この設計の利点は、BroadcastAlert 関数が特定の通知タイプについて知る必要も関知する必要もないことです。各タイプが Notifier 契約を満たしていることだけを要求します。後でブロードキャスト関数を一切変更することなく、SlackNotifier や EmailNotifier を追加することができます。
チートシート
Goにおけるインターフェースは、具体的な型を指定せずに必要な振る舞いを定義する契約として機能します。関数がインターフェース型を受け入れる場合、そのインターフェースのメソッドを実装している任意の型を使用できます。
メソッドのシグネチャを指定してインターフェースを定義します:
type DataSource interface {
FetchData() string
}インターフェースを受け入れる関数は、契約を満たすすべての型で動作します:
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}複数の型が同じインターフェースを実装できます:
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // 動作します!
ProcessData(api) // これも動作します!
}この契約ベースのアプローチにより、以下が可能になります:
- 特定の型ではなく振る舞いに焦点を当てた関数
- モック実装によるテスト容易性の向上
- 既存のコードを修正することなく拡張が可能
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert はすべての通知先を通じてメッセージを送信します
// TODO: この関数を以下のように実装してください:
// - Notifier 値のスライスとメッセージ string を受け取る
// - 各通知先で Notify を呼び出し、結果を出力する
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: 通知先をループで処理し、各通知結果を出力する
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// コンソールのプレフィックスを読み込む
scanner.Scan()
prefix := scanner.Text()
// ファイル名を読み込む
scanner.Scan()
filename := scanner.Text()
// webhook URL を読み込む
scanner.Scan()
url := scanner.Text()
// アラートメッセージを読み込む
scanner.Scan()
message := scanner.Text()
// TODO: 3つの通知先タイプすべてのインスタンスを作成する
// - プレフィックスを指定した ConsoleNotifier
// - ファイル名を指定した FileNotifier
// - URL を指定した WebhookNotifier
// TODO: 3つすべての通知先を含む Notifier のスライスを作成する
// TODO: 通知先とメッセージを指定して BroadcastAlert を呼び出す
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機4インターフェース
インターフェース入門暗黙的な実装契約としてのインターフェース空のインターフェース (any)型アサーション型スイッチインターフェースの合成Stringer と Error インターフェースまとめ - 図形計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー