Estou familiarizado com o fato de que, em Go, as interfaces definem funcionalidade, em vez de dados. Você coloca um conjunto de métodos em uma interface, mas não consegue especificar nenhum campo que seria necessário em qualquer coisa que implemente essa interface.
Por exemplo:
// 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
}
Agora podemos usar a interface e suas implementações:
// 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
*/
Agora, o que você não pode fazer é algo assim:
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)
}
No entanto, depois de brincar com interfaces e estruturas incorporadas, descobri uma maneira de fazer isso, de certa forma:
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
}
Por causa da estrutura incorporada, Bob tem tudo que Person tem. Ele também implementa a interface PersonProvider, para que possamos passar Bob para funções projetadas para usar essa interface.
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)
}
Aqui está um Go Playground que demonstra o código acima.
Usando esse método, posso fazer uma interface que define dados em vez de comportamento, e que pode ser implementada por qualquer estrutura apenas incorporando esses dados. Você pode definir funções que interagem explicitamente com esses dados incorporados e não estão cientes da natureza da estrutura externa. E tudo é verificado em tempo de compilação! (A única maneira de você bagunçar, pelo que eu posso ver, seria incorporar a interface PersonProvider
em Bob
, em vez de um concreto Person
. Ele iria compilar e falhar no tempo de execução.)
Agora, aqui está a minha pergunta: este é um truque legal ou eu deveria fazer diferente?
Respostas:
É definitivamente um truque legal. No entanto, a exposição de ponteiros ainda disponibiliza acesso direto aos dados, de modo que só oferece flexibilidade adicional limitada para alterações futuras. Além disso, as convenções Go não exigem que você sempre coloque uma abstração na frente dos atributos de dados .
Juntando essas coisas, eu tenderia para um extremo ou outro para um determinado caso de uso: ou a) apenas crie um atributo público (usando incorporação, se aplicável) e passe tipos concretos ou b) se parecer que expor os dados, causar problemas mais tarde, exponha um getter / setter para uma abstração mais robusta.
Você vai pesar isso por atributo. Por exemplo, se alguns dados são específicos da implementação ou você espera alterar as representações por algum outro motivo, provavelmente não deseja expor o atributo diretamente, enquanto outros atributos de dados podem ser estáveis o suficiente para torná-los públicos uma vitória líquida.
Ocultar propriedades atrás de getters e setters oferece flexibilidade extra para fazer alterações compatíveis com versões anteriores posteriormente. Digamos que algum dia você queira alterar
Person
para armazenar não apenas um único campo de "nome", mas o primeiro / meio / último / prefixo; se você tem métodosName() string
eSetName(string)
, pode manter os usuários existentes daPerson
interface felizes enquanto adiciona novos métodos mais refinados. Ou você pode querer marcar um objeto de banco de dados como "sujo" quando ele tem alterações não salvas; você pode fazer isso quando as atualizações de dados passarem porSetFoo()
métodos.Assim: com getters / setters, você pode alterar os campos de estrutura enquanto mantém uma API compatível e adicionar lógica em torno de get / sets de propriedade, já que ninguém pode simplesmente
p.Name = "bob"
passar sem passar pelo seu código.Essa flexibilidade é mais relevante quando o tipo é complicado (e a base de código é grande). Se você tiver um
PersonCollection
, ele pode ser apoiado internamente por umsql.Rows
, um[]*Person
, um[]uint
de IDs de banco de dados ou qualquer outro. Usando a interface certa, você pode evitar que os chamadores se importem com isso, da maneira comoio.Reader
as conexões de rede e os arquivos parecem semelhantes.Uma coisa específica:
interface
s no Go têm a propriedade peculiar de que você pode implementar um sem importar o pacote que o define; que pode ajudá-lo a evitar importações cíclicas . Se sua interface retornar um*Person
, ao invés de apenas strings ou o que for, todosPersonProviders
terão que importar o pacote ondePerson
está definido. Isso pode ser bom ou mesmo inevitável; é apenas uma consequência a ser conhecida.Mas, novamente, a comunidade Go não tem uma convenção forte contra a exposição de membros de dados na API pública do seu tipo . É deixado a seu critério se é razoável usar o acesso público a um atributo como parte de sua API em um determinado caso, em vez de desencorajar qualquer exposição porque isso poderia complicar ou impedir uma mudança de implementação posteriormente.
Assim, por exemplo, o stdlib faz coisas como permitir que você inicialize um
http.Server
com sua configuração e promete que um zerobytes.Buffer
pode ser usado. É bom fazer suas próprias coisas desse jeito e, de fato, não acho que você deva abstrair as coisas preventivamente se a versão mais concreta, expondo os dados, parece funcionar. É apenas uma questão de estar ciente das vantagens e desvantagens.fonte
Se bem entendi, você deseja preencher os campos de um struct em outro. Minha opinião é não usar interfaces para estender. Você pode fazer isso facilmente pela próxima abordagem.
https://play.golang.org/p/aBJ5fq3uXtt
Nota
Person
naBob
declaração. Isso fará com que o campo de estrutura incluído esteja disponível naBob
estrutura diretamente com algum açúcar sintático.fonte