Goでは、インターフェイスはデータではなく機能を定義するという事実に精通しています。一連のメソッドをインターフェイスに配置しますが、そのインターフェイスを実装するものに必要なフィールドを指定することはできません。
例えば:
// Interface
type Giver interface {
Give() int64
}
// One implementation
type FiveGiver struct {}
func (fg *FiveGiver) Give() int64 {
return 5
}
// Another implementation
type VarGiver struct {
number int64
}
func (vg *VarGiver) Give() int64 {
return vg.number
}
これで、インターフェイスとその実装を使用できます。
// A function that uses the interface
func GetSomething(aGiver Giver) {
fmt.Println("The Giver gives: ", aGiver.Give())
}
// Bring it all together
func main() {
fg := &FiveGiver{}
vg := &VarGiver{3}
GetSomething(fg)
GetSomething(vg)
}
/*
Resulting output:
5
3
*/
さて、あなたができないは次のようなものです:
type Person interface {
Name string
Age int64
}
type Bob struct implements Person { // Not Go syntax!
...
}
func PrintName(aPerson Person) {
fmt.Println("Person's name is: ", aPerson.Name)
}
func main() {
b := &Bob{"Bob", 23}
PrintName(b)
}
ただし、インターフェイスと埋め込み構造体をいじってみたところ、次のような方法でこれを行う方法を発見しました。
type PersonProvider interface {
GetPerson() *Person
}
type Person struct {
Name string
Age int64
}
func (p *Person) GetPerson() *Person {
return p
}
type Bob struct {
FavoriteNumber int64
Person
}
構造体が埋め込まれているため、BobにはPersonのすべてが含まれています。また、PersonProviderインターフェースも実装しているため、そのインターフェースを使用するように設計された関数にBobを渡すことができます。
func DoBirthday(pp PersonProvider) {
pers := pp.GetPerson()
pers.Age += 1
}
func SayHi(pp PersonProvider) {
fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}
func main() {
b := &Bob{
5,
Person{"Bob", 23},
}
DoBirthday(b)
SayHi(b)
fmt.Printf("You're %v years old now!", b.Age)
}
これはGo Playgroundです 上記のコードを示しています。
このメソッドを使用して、動作ではなくデータを定義し、そのデータを埋め込むだけで任意の構造体によって実装できるインターフェイスを作成できます。その埋め込みデータと明示的に対話し、外部構造の性質を認識しない関数を定義できます。そして、コンパイル時にすべてがチェックされます! (混乱する唯一の方法は、具体的なPersonProvider
ではなく、Bob
にPerson
インターフェースを埋め込むことです。コンパイルして失敗します。ランタイム。)
さて、ここに私の質問があります:これはきちんとしたトリックですか、それとも違う方法でやるべきですか?
これは間違いなくきちんとしたトリックであり、APIの一部としてそれらのフィールドへのアクセスを許可することができれば機能します。私が検討する代替案は、埋め込み可能なstruct/interface
セットアップを維持することですが、ゲッターとセッターの観点からインターフェースを定義します。
ゲッターとセッターの背後にあるプロパティを非表示にすることで、後方互換性のある変更を後で行うための柔軟性が追加されます。いつかPerson
を変更して、単一の「名前」フィールドだけでなく、first/middle/last/prefixを保存するとします。メソッドName() string
およびSetName(string)
がある場合は、Person
インターフェースの既存のユーザーを満足させ、新しいきめ細かいメソッドを追加できます。または、保存されていない変更がある場合、データベースでバックアップされたオブジェクトを「ダーティ」としてマークすることができます。データの更新がすべてSetFoo()
メソッドを通過するときに行うことができます。
そのため、ゲッター/セッターを使用すると、互換性のあるAPIを維持しながら構造体フィールドを変更し、コードを介さずにp.Name = "bob"
を実行することはできないため、プロパティの取得/設定に関するロジックを追加できます。
あなたのタイプがもっと複雑なことをするとき、その柔軟性はより適切です。 PersonCollection
がある場合、sql.Rows
、[]*Person
、[]uint
のデータベースIDなどによって内部的にサポートされている可能性があります。適切なインターフェースを使用すると、io.Reader
がネットワーク接続とファイルの外観を同じようにする方法で、呼び出し元を気にする必要がなくなります。
具体的には、Goのinterface
sには、それを定義するパッケージをインポートせずに実装できる独特のプロパティがあります。それはあなたを助けることができます 循環インポートを避ける 。インターフェースが文字列だけではなく*Person
を返す場合、すべてのPersonProviders
はPerson
が定義されているパッケージをインポートする必要があります。それは問題ないか、避けられないことさえあります。知っておくだけの結果です。
とはいえ、すべてのデータを非表示にする必要のあるGoの規則はありません。 (これは、たとえばC++との歓迎すべき違いです。)stdlibは、設定でhttp.Server
を初期化し、ゼロbytes.Buffer
が使用可能であることを約束するなどのことを行います。そのような独自の作業を行うのは問題ありません。実際、より具体的なデータ公開バージョンが機能する場合、時期尚早な抽象化を行う必要はないと思います。トレードオフを意識するだけです。