Перейти Полей Интерфейс
Я знаком с тем, что в 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
}
из-за встроенной структуры у Боба есть все, что есть у человека. Он также реализует интерфейс 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)
}
вот вам игровая площадка что демонстрирует приведенный выше код.
используя этот метод, я могу сделать интерфейс, который определяет данные, а не поведение, и которые могут быть реализованы любой структурой только путем встраивания этих данных. Вы можете определить функции, которые явно взаимодействуют с этими внедренными данными и не знают о природе внешней структуры. И все проверяется во время компиляции! (Единственный способ, которым вы могли бы испортить, что я вижу, было бы встраивание интерфейса PersonProvider на Bob, а не конкретный Person. Он будет компилироваться и не во время выполнения.)
теперь, вот мой вопрос: это ловкий трюк, или я должен делать это по-другому?
1 ответ:
это, безусловно, аккуратный трюк, и работает до тех пор, пока вы прохладно давая доступ к этим полям в рамках вашего API. Альтернативой, которую я бы рассмотрел, является сохранение встраиваемой структуры/
interfaceнастройка, но определение интерфейса с точки зрения геттеров и сеттеров.скрытие свойств за геттерами и сеттерами дает вам дополнительную гибкость для внесения обратно совместимых изменений позже. Скажите, вы когда-нибудь хотите изменить
Personдля хранения не только одного поля "имя", но первый/средний/последний/префикс; если у вас есть методыName() stringиSetName(string), вы можете сохранить существующих пользователейPersonинтерфейс счастлив при добавлении новых мелкозернистых методов. Или вы можете захотеть отметить объект, поддерживаемый базой данных, как "грязный", когда он имеет несохраненные изменения; вы можете сделать это, когда все обновления данных проходят черезSetFoo()методы.Итак: с помощью геттеров / сеттеров вы можете изменять поля структуры при сохранении совместимого API и добавлять логику вокруг свойства get / sets, так как нет можно просто сделать
p.Name = "bob"не проходя через ваш код.эта гибкость более актуальна, когда ваш тип делает что-то более сложное. Если у вас есть
PersonCollection, он может быть внутренне подкрепленsql.Rows, a[]*Person, a[]uintидентификаторов баз данных, или что-то еще. Используя правильный интерфейс, вы можете спасти абонентов от заботы, которая есть, кстатиio.Readerсетевые подключения и файлы выглядят одинаково.одна конкретная вещь:
interfaces в Go имеют своеобразный свойство, которое можно реализовать без импорта пакета, который его определяет; это может помочь вам избежать циклического импорта. Если ваш интерфейс возвращает*Person, вместо того, чтобы просто строки или что-то еще, всеPersonProvidersдолжны импортировать пакет, гдеPersonопределяется. Это может быть хорошо или даже неизбежно; это просто следствие, о котором нужно знать.все, что сказал, нет никакого соглашения Go, что вы должны скрыть все свои данные. (Это долгожданное отличие, скажем, С.++) Stdlib делает такие вещи, как позволяет инициализировать
http.ServerС вашей конфигурацией и обещает, что нольbytes.Bufferпригоден для использования. Это прекрасно, чтобы делать свои собственные вещи, как это, и, действительно, я не думаю, что вы должны делать преждевременную абстракцию, если работает более конкретная, разоблачающая данные версия. Это просто осознание компромиссов.