Menu
Coddy logo textTech

Regras de Satisfação de Interfaces

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

Embora Go use tipagem pato para a satisfação implícita de interfaces, há regras específicas que determinam se um tipo realmente satisfaz uma interface. Entender essas regras ajuda você a evitar bugs sutis.

A regra mais importante envolve receptores de ponteiro. Se um método for definido com um receptor de ponteiro, apenas um ponteiro para esse tipo satisfaz a interface, não o próprio valor:

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() tem um receptor ponteiro, apenas *Document satisfaz Saver:

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

Entretanto, o inverso é mais flexível. Se um método tiver um receptor de valor, tanto os tipos valor quanto ponteiro satisfazem a interface. Go remove automaticamente a referência dos ponteiros ao chamar métodos com receptores 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 Go sempre pode obter um valor a partir de um ponteiro (por 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 criar um sistema de configuração que demonstra como receptores de ponteiro e de valor afetam a satisfação de uma interface. Você criará tipos em que 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 exija 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 aceite uma 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 (obrigató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 a ser definido para ReadOnlyConfig
  • Linha 3: valor inicial para Setting
  • Linha 4: novo valor a ser tentado para 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). A principal conclusão é que valores de ReadOnlyConfig sozinhos não satisfariam Configurable, apenas ponteiros o fazem, enquanto valores de Setting funcionam diretamente porque todos os seus métodos usam receptores de valor.

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: Crie um ReadOnlyConfig com o valor rocInitial
	// TODO: Passe um PONTEIRO para ProcessConfig (necessário porque SetValue tem receptor de ponteiro)
	
	// TODO: Crie um Setting com o valor settingInitial
	// TODO: Passe 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ável não utilizada
	_ = 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

Pratique por conta própria: Compilador de Go online