Перейти Полей Интерфейс
Я знаком с тем, что в 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
сетевые подключения и файлы выглядят одинаково.одна конкретная вещь:
interface
s в Go имеют своеобразный свойство, которое можно реализовать без импорта пакета, который его определяет; это может помочь вам избежать циклического импорта. Если ваш интерфейс возвращает*Person
, вместо того, чтобы просто строки или что-то еще, всеPersonProviders
должны импортировать пакет, гдеPerson
определяется. Это может быть хорошо или даже неизбежно; это просто следствие, о котором нужно знать.все, что сказал, нет никакого соглашения Go, что вы должны скрыть все свои данные. (Это долгожданное отличие, скажем, С.++) Stdlib делает такие вещи, как позволяет инициализировать
http.Server
С вашей конфигурацией и обещает, что нольbytes.Buffer
пригоден для использования. Это прекрасно, чтобы делать свои собственные вещи, как это, и, действительно, я не думаю, что вы должны делать преждевременную абстракцию, если работает более конкретная, разоблачающая данные версия. Это просто осознание компромиссов.