Menu
Coddy logo textTech

Regras de Satisfação de Interface

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 43 de 107.

Embora o Go utilize duck typing para a satisfação implícita de interfaces, existem regras específicas que determinam se um tipo realmente satisfaz uma interface. Compreender essas regras ajuda você a evitar bugs sutis.

A regra mais importante envolve recebedores de ponteiro. Se um método for definido com um recebedor de ponteiro, apenas um ponteiro para esse tipo satisfaz a interface — não o valor em si:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // receptor de ponteiro
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Como Save() possui um receptor de ponteiro, apenas *Document satisfaz Saver:

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // funciona - o ponteiro satisfaz a interface
    // Process(doc)  // erro de compilação - o valor não satisfaz a interface
}

No entanto, o inverso é mais flexível. Se um método possui um receptor de valor, ambos os tipos de valor e ponteiro satisfazem a interface. O Go desreferencia ponteiros automaticamente ao chamar métodos com receptor de valor:

func (d Document) Info() string {  // receptor de valor
    return d.Name
}

// Tanto Document quanto *Document satisfazem uma interface que requer Info()

Essa assimetria existe porque o Go sempre pode obter um valor a partir de um ponteiro (por meio da desreferenciação), mas nem sempre pode obter um ponteiro a partir de um valor (o valor pode não ser endereçável). Manter essa regra em mente evita erros confusos do compilador ao trabalhar com interfaces.

challenge icon

Desafio

Fácil

Vamos construir um sistema de configuração que demonstra como os receptores de ponteiro e de valor afetam a satisfação de uma interface. Você criará tipos onde a escolha do receptor determina se valores, ponteiros ou ambos podem ser usados com uma interface.

Você organizará seu código em três arquivos:

  • config.go: Defina uma interface Configurable que requer dois métodos: GetValue() string e SetValue(string). Em seguida, crie dois tipos de configuração:
    • ReadOnlyConfig com um campo Value—use um receptor de valor para GetValue() (retorna o Value) e um receptor de ponteiro para SetValue() (atualiza o Value)
    • Setting com um campo Data—use receptores de valor para ambos os métodos (GetValue retorna Data, SetValue apenas imprime "Cannot modify" sem alterar nada)
  • processor.go: Crie uma função chamada ProcessConfig que aceita um Configurable e uma nova string de valor. Ela deve imprimir o valor atual usando GetValue(), chamar SetValue() com o novo valor e, em seguida, imprimir o valor novamente para mostrar quaisquer alterações.
  • main.go: Leia os detalhes da configuração da entrada e demonstre as regras de satisfação da interface:
    1. Crie um ReadOnlyConfig e passe um ponteiro para ProcessConfig (necessário porque SetValue tem um receptor de ponteiro)
    2. Crie um Setting e passe o valor diretamente para ProcessConfig (funciona porque ambos os métodos têm receptores de valor)

As seguintes entradas serão fornecidas:

  • Linha 1: Valor inicial para ReadOnlyConfig
  • Linha 2: Novo valor para definir em ReadOnlyConfig
  • Linha 3: Valor inicial para Setting
  • Linha 4: Novo valor para tentar em Setting

Sua função ProcessConfig deve imprimir neste formato:

Current: [value]
Current: [value after SetValue]

Por exemplo, dados debug, production, localhost e remote, sua saída deve ser:

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Observe como ReadOnlyConfig realmente altera seu valor (porque passamos um ponteiro), enquanto Setting permanece inalterado (seu SetValue com um receptor de valor não pode modificar o original). O ponto principal é que os valores de ReadOnlyConfig sozinhos não satisfariam Configurable—apenas os ponteiros satisfazem—enquanto os valores de Setting funcionam diretamente porque todos os seus métodos usam receptores de valor.

Folha de consulta

Go utiliza duck typing para a satisfação implícita de interfaces, mas existem regras específicas sobre receptores de ponteiro e de valor:

Regra do Receptor de Ponteiro

Se um método possui um receptor de ponteiro (pointer receiver), apenas um ponteiro para esse tipo satisfaz a interface:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // pointer receiver
    return "Saved: " + d.Name
}

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // works - pointer satisfies interface
    // Process(doc)  // compile error - value doesn't satisfy interface
}

Regra do Receptor de Valor

Se um método possui um receptor de valor (value receiver), tanto o tipo de valor quanto o tipo de ponteiro satisfazem a interface:

func (d Document) Info() string {  // value receiver
    return d.Name
}

// Both Document and *Document satisfy an interface requiring Info()

Por que essa assimetria existe

O Go sempre pode obter um valor a partir de um ponteiro (através da desreferenciação), mas nem sempre pode obter um ponteiro a partir de um valor (o valor pode não ser endereçável).

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Ler entradas
	scanner.Scan()
	rocInitial := scanner.Text()
	scanner.Scan()
	rocNew := scanner.Text()
	scanner.Scan()
	settingInitial := scanner.Text()
	scanner.Scan()
	settingNew := scanner.Text()
	
	// TODO: Criar um ReadOnlyConfig com o valor rocInitial
	// TODO: Passar um PONTEIRO para ProcessConfig (necessário porque SetValue tem receptor de ponteiro)
	
	// TODO: Criar um Setting com o valor settingInitial
	// TODO: Passar o VALOR diretamente para ProcessConfig (funciona porque ambos os métodos têm receptores de valor)
	
	// Use estas variáveis para evitar erros de variáveis não utilizadas
	_ = rocInitial
	_ = rocNew
	_ = settingInitial
	_ = settingNew
}
quiz iconTeste seus conhecimentos

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