Promoção de Métodos
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 36 de 107.
Assim como a incorporação de structs promove campos para a struct externa, o Go também promove métodos de tipos incorporados. Isso significa que você pode chamar métodos definidos na struct incorporada diretamente na struct externa.
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // incorporado
}
Como a Car incorpora a Engine, o método Start() é promovido automaticamente:
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // também funciona
}
Essa promoção tem uma consequência importante para interfaces. Se o tipo incorporado satisfaz uma interface, o tipo externo automaticamente a satisfaz também:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car satisfaz Starter através de Engine
}
O tipo Car nunca implementa explicitamente Starter, mas ele satisfaz a interface porque seu Engine incorporado possui o método necessário. É assim que o Go alcança o reuso de comportamento sem a herança tradicional.
Desafio
FácilVamos construir um sistema de player de música que demonstra como os métodos de tipos incorporados (embedded) são promovidos para o tipo externo e como isso permite a satisfação de interfaces por meio da composição.
Você organizará seu código em dois arquivos:
audio.go: Crie os blocos de construção para o seu player de música:- Uma struct
AudioPlayercom um campoBrand(string) e um métodoPlay() stringque retorna[Brand] playing audio - Uma interface
Playableque exige um métodoPlay() string - Uma struct
Smartphonecom um campoModelque incorporaAudioPlayer - Uma struct
Tabletcom um campoModelque incorporaAudioPlayer
- Uma struct
main.go: Crie uma função chamadaStartPlaybackque aceita qualquerPlayablee retorna o resultado da chamada dePlay(). Leia as informações do dispositivo a partir da entrada, crie tanto um Smartphone quanto um Tablet, e demonstre que ambos satisfazem a interfacePlayablepor meio de seuAudioPlayerincorporado — mesmo que nenhum dos tipos implemente explicitamente o métodoPlay()por si só.
As seguintes entradas serão fornecidas:
- Linha 1: Nome do modelo do Smartphone
- Linha 2: Marca de áudio do Smartphone
- Linha 3: Nome do modelo do Tablet
- Linha 4: Marca de áudio do Tablet
Para cada dispositivo, imprima seu modelo seguido pelo resultado de passá-lo para StartPlayback.
Por exemplo, dados iPhone 15, Apple Audio, iPad Pro e Beats, sua saída deve ser:
iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audioObserve como tanto Smartphone quanto Tablet podem ser passados para StartPlayback porque o método Play() de AudioPlayer é automaticamente promovido para cada tipo externo, satisfazendo a interface Playable sem qualquer código adicional.
Folha de consulta
Quando uma struct incorpora outro tipo, os métodos do tipo incorporado são automaticamente promovidos para a struct externa:
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // incorporado
}
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // também funciona
}
Se um tipo incorporado satisfaz uma interface, o tipo externo automaticamente a satisfaz também:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car satisfaz Starter através de Engine
}
Isso permite o reuso de comportamento através de composição sem a herança tradicional.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Crie uma função StartPlayback que aceite qualquer Playable
// e retorne o resultado da chamada de Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler o modelo do smartphone
scanner.Scan()
smartphoneModel := scanner.Text()
// Ler a marca de áudio do smartphone
scanner.Scan()
smartphoneBrand := scanner.Text()
// Ler o modelo do tablet
scanner.Scan()
tabletModel := scanner.Text()
// Ler a marca de áudio do tablet
scanner.Scan()
tabletBrand := scanner.Text()
// TODO: Crie um Smartphone com o AudioPlayer incorporado
// TODO: Crie um Tablet com o AudioPlayer incorporado
// TODO: Imprima o modelo do smartphone e, em seguida, chame StartPlayback com o smartphone
// TODO: Imprima o modelo do tablet e, em seguida, chame StartPlayback com o tablet
_ = smartphoneModel
_ = smartphoneBrand
_ = tabletModel
_ = tabletBrand
fmt.Println("TODO: Complete the implementation")
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool