Я знаком с тем фактом, что в 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
}
Благодаря встроенной структуре у Боба есть все, что есть у Person. Он также реализует интерфейс PersonProvider, поэтому мы можем передать Боба функциям, которые предназначены для использования этого интерфейса.
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 , демонстрирующая приведенный выше код.
Используя этот метод, я могу создать интерфейс, который определяет данные, а не поведение, и который может быть реализован любой структурой, просто встраивая эти данные. Вы можете определять функции, которые явно взаимодействуют с этими встроенными данными и не знают о природе внешней структуры. И все проверяется во время компиляции! (Только так вы могли бы испортить, что я могу видеть, будет встраивание интерфейса PersonProvider
в Bob
, а не бетон Person
. Было бы компилировать и не во время выполнения.)
Теперь вот мой вопрос: это изящный трюк, или я должен делать это по-другому?
Ответы:
Это определенно изящный трюк. Однако раскрытие указателей по-прежнему делает доступным прямой доступ к данным, поэтому дает вам лишь ограниченную дополнительную гибкость для будущих изменений. Кроме того, соглашения Go не требуют, чтобы вы всегда помещали абстракцию перед атрибутами данных .
Взяв все это вместе, я бы склонялся к той или иной крайности для данного варианта использования: либо а) просто создайте общедоступный атрибут (используя встраивание, если применимо) и передавайте конкретные типы, либо б) если кажется, что раскрытие данных будет вызовет проблемы позже, выставьте геттер / сеттер для более надежной абстракции.
Вы будете взвешивать это на основе атрибутов. Например, если некоторые данные зависят от реализации или вы ожидаете изменить представление по какой-либо другой причине, вы, вероятно, не захотите раскрывать атрибут напрямую, тогда как другие атрибуты данных могут быть достаточно стабильными, чтобы сделать их общедоступными - это чистый выигрыш.
Скрытие свойств за геттерами и сеттерами дает вам дополнительную гибкость для внесения обратно совместимых изменений позже. Скажем, вы когда-нибудь захотите
Person
сохранить не только одно поле «name», но и первый / средний / последний / префикс; если у вас есть методыName() string
иSetName(string)
, вы можетеPerson
порадовать существующих пользователей интерфейса, добавляя новые более мелкие методы. Или вы можете захотеть пометить объект, поддерживаемый базой данных, как «грязный», если он имеет несохраненные изменения; это можно сделать, когда все обновления данных проходят черезSetFoo()
методы.Итак: с помощью геттеров / сеттеров вы можете изменять поля структуры, поддерживая совместимый API, и добавлять логику для получения / набора свойств, поскольку никто не может просто обойтись
p.Name = "bob"
без прохождения вашего кода.Эта гибкость более актуальна, когда тип сложный (и кодовая база большая). Если у вас есть
PersonCollection
, он может быть внутренне подкреплен идентификаторами базы данныхsql.Rows
, a[]*Person
, a[]uint
или чем-то еще. Используя правильный интерфейс, вы можете избавить вызывающих абонентов от заботы, которая такова, так какio.Reader
сетевые соединения и файлы выглядят одинаково.Одна особенность:
interface
s в Go имеют особенное свойство, которое вы можете реализовать без импорта пакета, который его определяет; это может помочь вам избежать циклического импорта . Если ваш интерфейс возвращает a*Person
, а не просто строки или что-то еще, всеPersonProviders
должны импортировать пакет, в которомPerson
он определен. Это может быть хорошо или даже неизбежно; это просто следствие, о котором нужно знать.Но опять же, у сообщества Go нет строгого соглашения против раскрытия членов данных в общедоступном API вашего типа . Это остается на ваше суждение , является ли это разумно использовать доступ общественности к атрибуту , как часть вашего API в данном случае, а не препятствуя любую экспозицию , так как это могло осложнить или предотвратить изменение реализации позже.
Так, например, stdlib делает такие вещи, как позволяет вам инициализировать
http.Server
файл с вашей конфигурацией и обещает, что можно использовать нольbytes.Buffer
. Это нормально - делать свои собственные вещи подобным образом, и, действительно, я не думаю, что вам следует упреждающе абстрагироваться от вещей, если более конкретная версия с раскрытием данных, вероятно, сработает. Это просто осознание компромиссов.источник
Если я правильно понимаю, вы хотите заполнить одно поле структуры в другое. Мое мнение - не использовать интерфейсы для расширения. Вы легко можете сделать это с помощью следующего подхода.
https://play.golang.org/p/aBJ5fq3uXtt
Отметьте
Person
вBob
декларации. Это сделает включенное поле структуры доступным вBob
структуре напрямую с некоторым синтаксическим сахаром.источник