Interfaces avançado
Esta aula explora conceitos avançados de interfaces em Go, incluindo type assertions, type switches, composição de interfaces e boas práticas. O conteúdo é focado em aplicações práticas e exemplos de código para alunos intermediários.
Nesta aula, vamos nos aprofundar em tópicos avançados de interfaces em Go. Você aprenderá como extrair e verificar tipos concretos de valores de interface usando type assertions, como usar type switches para lidar com múltiplos tipos de forma elegante, como compor interfaces para criar abstrações mais poderosas e, por fim, boas práticas para projetar e utilizar interfaces de maneira eficaz.
As interfaces em Go são uma ferramenta poderosa para polimorfismo e desacoplamento. Dominar esses conceitos avançados permitirá que você escreva código mais flexível, reutilizável e de fácil manutenção.
Type assertions
Uma type assertion é uma operação que permite acessar o valor concreto subjacente de um valor de interface. A sintaxe é valor.(Tipo), onde valor é uma interface e Tipo é o tipo concreto que você deseja extrair. Se a interface não contiver um valor do tipo especificado, a operação pode causar um pânico, a menos que você use a forma de duas variáveis.
A forma de duas variáveis v, ok := valor.(Tipo) retorna um booleano ok que indica se a asserção foi bem-sucedida, evitando pânico. Isso é útil para verificar dinamicamente o tipo de um valor.
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Type assertion com verificação
s, ok := i.(string)
if ok {
fmt.Println("Valor é string:", s)
} else {
fmt.Println("Valor não é string")
}
// Type assertion sem verificação (pode causar pânico)
// s2 := i.(string) // seguro, pois i é string
// n := i.(int) // pânico!
// Exemplo com múltiplos tipos
var val interface{} = 42
if str, ok := val.(string); ok {
fmt.Println("É string:", str)
} else if num, ok := val.(int); ok {
fmt.Println("É int:", num)
} else {
fmt.Println("Tipo desconhecido")
}
}
Type switch
Um type switch é uma construção que permite comparar o tipo concreto de uma interface com vários tipos possíveis. A sintaxe é switch v := valor.(type) { ... }. Dentro de cada case, a variável v é automaticamente convertida para o tipo correspondente, permitindo acesso direto aos seus campos e métodos.
Type switches são mais limpos e seguros do que uma sequência de type assertions. Eles são especialmente úteis quando você precisa tratar diferentes tipos de maneira específica, como em funções que processam entradas variadas.
package main
import "fmt"
func printType(val interface{}) {
switch v := val.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Int:", v)
case float64:
fmt.Println("Float64:", v)
default:
fmt.Println("Tipo desconhecido")
}
}
func main() {
printType("hello")
printType(42)
printType(3.14)
printType(true)
}
Composição de interfaces
Go permite compor interfaces a partir de outras interfaces, criando interfaces maiores que exigem a implementação de todos os métodos das interfaces incorporadas. Isso é feito simplesmente listando as interfaces dentro da definição da nova interface, sem métodos adicionais. A composição promove reutilização e permite construir abstrações hierárquicas.
Por exemplo, podemos ter uma interface Reader e Writer e compô-las em ReadWriter. Qualquer tipo que implemente ambos os conjuntos de métodos automaticamente satisfaz a interface composta.
package main
import "fmt"
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// Composição de interfaces
type ReadWriter interface {
Reader
Writer
}
// Implementação concreta
type MyReadWriter struct{}
func (m MyReadWriter) Read(p []byte) (n int, err error) {
return len(p), nil
}
func (m MyReadWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func main() {
var rw ReadWriter = MyReadWriter{}
fmt.Println(rw)
}
Boas práticas
Ao trabalhar com interfaces em Go, algumas práticas recomendadas incluem: definir interfaces pequenas e focadas (princípio da segregação de interfaces), aceitar interfaces e retornar structs (para flexibilidade e simplicidade), e usar interfaces apenas quando necessário (evitar abstrações prematuras).
Além disso, prefira type switches a longas cadeias de type assertions, e evite usar interfaces vazias (interface{}) a menos que seja realmente necessário (por exemplo, para funções genéricas). Lembre-se de que interfaces em Go são satisfeitas implicitamente; documente bem os contratos esperados.
package main
import "fmt"
// Boa prática: interface pequena e focada
type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d anos)", p.Name, p.Age)
}
func main() {
var s Stringer = Person{"Alice", 30}
fmt.Println(s.String())
}
Referências
- Effective Go: Interfaces
- Go Language Specification: Interface types
- Go Blog: Type assertions and type switches
- Go FAQ: Interfaces
- Package fmt: Stringer interface
- A Tour of Go: Type switches
Exercícios
-
Escreva uma função que receba um valor do tipo
interface{}e retorne uma string descrevendo o tipo: "string", "int", "float64", "bool" ou "outro". Use type switch.✓ Resposta:func describeType(val interface{}) string { switch val.(type) { case string: return "string" case int: return "int" case float64: return "float64" case bool: return "bool" default: return "outro" } } -
Crie uma interface
Shapecom métodoArea() float64. Depois, crie uma interfacePerimetercom métodoPerimeter() float64. Componha ambas emGeometry. Implemente essas interfaces para um tipoRectangle(com campos Largura e Altura).✓ Resposta:type Shape interface { Area() float64 } type Perimeter interface { Perimeter() float64 } type Geometry interface { Shape Perimeter } type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) } -
Escreva um programa que use type assertion para verificar se um valor
interface{}é uminte, se for, imprima seu quadrado. Use a forma segura (comok).✓ Resposta:package main import "fmt" func main() { var val interface{} = 5 if num, ok := val.(int); ok { fmt.Println(num * num) } else { fmt.Println("não é int") } } -
Explique por que é preferível usar type switch em vez de múltiplas type assertions quando se tem vários tipos possíveis. Dê um exemplo de código que ilustre a diferença.
✓ Resposta:Type switch é mais limpo e menos repetitivo. Com múltiplas type assertions, você precisa escrever várias verificações if-else e repetir a variável. Exemplo:
// Com type switch func process(val interface{}) { switch v := val.(type) { case string: fmt.Println("String:", v) case int: fmt.Println("Int:", v) } } // Com múltiplas type assertions func process2(val interface{}) { if v, ok := val.(string); ok { fmt.Println("String:", v) } else if v, ok := val.(int); ok { fmt.Println("Int:", v) } } -
Implemente uma função que aceite um
io.Writere escreva nele uma mensagem de saudação. Em seguida, chame essa função comos.Stdoute com um buffer de string (bytes.Buffer). Dica: use a interfaceio.Writerda biblioteca padrão.✓ Resposta:package main import ( "bytes" "fmt" "io" "os" ) func greet(w io.Writer) { fmt.Fprintln(w, "Olá, mundo!") } func main() { greet(os.Stdout) var buf bytes.Buffer greet(&buf) fmt.Print(buf.String()) }