Use when Go interfaces including interface design, duck typing, and composition patterns. Use when designing Go APIs and abstractions.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
name: go-interfaces description: Use when Go interfaces including interface design, duck typing, and composition patterns. Use when designing Go APIs and abstractions. allowed-tools:
Master Go's interface system for creating flexible, decoupled code through implicit implementation and composition patterns.
Defining and implementing interfaces:
package main
import "fmt"
// Define interface
type Writer interface {
Write(p []byte) (n int, err error)
}
// Implement interface (implicit)
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(p []byte) (n int, err error) {
fmt.Print(string(p))
return len(p), nil
}
func main() {
var w Writer = ConsoleWriter{}
w.Write([]byte("Hello, World!\n"))
}
Multiple methods in interface:
type Reader interface {
Read(p []byte) (n int, err error)
}
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
// Implement ReadWriter
type File struct {
name string
}
func (f *File) Read(p []byte) (n int, err error) {
// Implementation
return 0, nil
}
func (f *File) Write(p []byte) (n int, err error) {
// Implementation
return len(p), nil
}
Using interface{} (any in Go 1.18+):
// Accepts any type
func printValue(v interface{}) {
fmt.Println(v)
}
// Modern syntax (Go 1.18+)
func printAny(v any) {
fmt.Println(v)
}
func main() {
printValue(42)
printValue("hello")
printValue(true)
printAny(3.14)
}
Type assertions:
func processValue(v interface{}) {
// Type assertion
if str, ok := v.(string); ok {
fmt.Println("String:", str)
}
// Type switch
switch val := v.(type) {
case int:
fmt.Println("Integer:", val)
case string:
fmt.Println("String:", val)
case bool:
fmt.Println("Boolean:", val)
default:
fmt.Println("Unknown type")
}
}
Embedding interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// Standard library example
import "io"
func useReadWriteCloser(rwc io.ReadWriteCloser) {
// Can call Read, Write, and Close
rwc.Write([]byte("data"))
rwc.Close()
}
Standard library interfaces:
// Stringer interface
type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
// error interface
type error interface {
Error() string
}
type MyError struct {
Message string
}
func (e MyError) Error() string {
return e.Message
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type ByAge []Person
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
Small interfaces:
// Good: small, focused interfaces
type Getter interface {
Get(key string) (value string, exists bool)
}
type Setter interface {
Set(key, value string)
}
type Deleter interface {
Delete(key string)
}
// Compose as needed
type Cache interface {
Getter
Setter
Deleter
}
Accept interfaces, return structs:
// Accept interface parameter
func processReader(r io.Reader) error {
data, err := io.ReadAll(r)
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
// Return concrete type
func newConfig() *Config {
return &Config{
Host: "localhost",
Port: 8080,
}
}
type Config struct {
Host string
Port int
}
Understanding nil interfaces:
func checkNil() {
var i interface{}
fmt.Println(i == nil) // true
var p *Person
i = p
fmt.Println(i == nil) // false! (type is set, value is nil)
// Proper nil check
v, ok := i.(*Person)
fmt.Println(v == nil, ok) // true, true
}
Checking interface implementation:
// Compile-time check
var _ io.Writer = (*MyWriter)(nil)
var _ io.Reader = (*MyReader)(nil)
type MyWriter struct{}
func (w *MyWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
// If MyWriter doesn't implement Writer, compilation fails
Implicit interface satisfaction:
// No explicit "implements" keyword needed
type Duck interface {
Quack()
Walk()
}
type RealDuck struct{}
func (d RealDuck) Quack() {
fmt.Println("Quack!")
}
func (d RealDuck) Walk() {
fmt.Println("Waddle waddle")
}
type Robot struct{}
func (r Robot) Quack() {
fmt.Println("Beep boop quack")
}
func (r Robot) Walk() {
fmt.Println("*mechanical walking sounds*")
}
func makeDuckDoThings(d Duck) {
d.Quack()
d.Walk()
}
func main() {
makeDuckDoThings(RealDuck{})
makeDuckDoThings(Robot{})
}
Using interfaces for polymorphism:
type Shape interface {
Area() float64
Perimeter() float64
}
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)
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func printShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{
Rectangle{Width: 10, Height: 5},
Circle{Radius: 7},
}
for _, shape := range shapes {
printShapeInfo(shape)
}
}
Using interfaces for testability:
// Define interface for dependency
type UserRepository interface {
GetUser(id int) (*User, error)
SaveUser(user *User) error
}
// Production implementation
type PostgresUserRepo struct {
db *sql.DB
}
func (r *PostgresUserRepo) GetUser(id int) (*User, error) {
// Database query
return &User{}, nil
}
func (r *PostgresUserRepo) SaveUser(user *User) error {
// Database insert/update
return nil
}
// Test implementation
type MockUserRepo struct {
users map[int]*User
}
func (m *MockUserRepo) GetUser(id int) (*User, error) {
user, exists := m.users[id]
if !exists {
return nil, errors.New("user not found")
}
return user, nil
}
func (m *MockUserRepo) SaveUser(user *User) error {
m.users[user.ID] = user
return nil
}
// Service depends on interface, not concrete type
type UserService struct {
repo UserRepository
}
func (s *UserService) GetUserName(id int) (string, error) {
user, err := s.repo.GetUser(id)
if err != nil {
return "", err
}
return user.Name, nil
}
type User struct {
ID int
Name string
}
Fluent interface pattern:
type QueryBuilder interface {
Select(fields ...string) QueryBuilder
From(table string) QueryBuilder
Where(condition string) QueryBuilder
Build() string
}
type sqlQueryBuilder struct {
selectFields []string
fromTable string
whereClause string
}
func NewQueryBuilder() QueryBuilder {
return &sqlQueryBuilder{}
}
func (b *sqlQueryBuilder) Select(fields ...string) QueryBuilder {
b.selectFields = fields
return b
}
func (b *sqlQueryBuilder) From(table string) QueryBuilder {
b.fromTable = table
return b
}
func (b *sqlQueryBuilder) Where(condition string) QueryBuilder {
b.whereClause = condition
return b
}
func (b *sqlQueryBuilder) Build() string {
query := "SELECT " + strings.Join(b.selectFields, ", ")
query += " FROM " + b.fromTable
if b.whereClause != "" {
query += " WHERE " + b.whereClause
}
return query
}
func main() {
query := NewQueryBuilder().
Select("id", "name", "email").
From("users").
Where("age > 18").
Build()
fmt.Println(query)
}
Implementing strategy pattern:
type PaymentStrategy interface {
Pay(amount float64) error
}
type CreditCardPayment struct {
CardNumber string
}
func (c *CreditCardPayment) Pay(amount float64) error {
fmt.Printf("Paying %.2f with credit card %s\n",
amount, c.CardNumber)
return nil
}
type PayPalPayment struct {
Email string
}
func (p *PayPalPayment) Pay(amount float64) error {
fmt.Printf("Paying %.2f via PayPal to %s\n",
amount, p.Email)
return nil
}
type ShoppingCart struct {
paymentMethod PaymentStrategy
}
func (cart *ShoppingCart) SetPaymentMethod(pm PaymentStrategy) {
cart.paymentMethod = pm
}
func (cart *ShoppingCart) Checkout(amount float64) error {
return cart.paymentMethod.Pay(amount)
}
func main() {
cart := &ShoppingCart{}
cart.SetPaymentMethod(&CreditCardPayment{CardNumber: "1234-5678"})
cart.Checkout(100.00)
cart.SetPaymentMethod(&PayPalPayment{Email: "user@example.com"})
cart.Checkout(50.00)
}
Adapting interfaces:
// Third-party logger
type ThirdPartyLogger struct{}
func (t *ThirdPartyLogger) LogMessage(msg string, level int) {
fmt.Printf("[Level %d] %s\n", level, msg)
}
// Our application interface
type Logger interface {
Info(msg string)
Error(msg string)
}
// Adapter
type LoggerAdapter struct {
thirdParty *ThirdPartyLogger
}
func (a *LoggerAdapter) Info(msg string) {
a.thirdParty.LogMessage(msg, 0)
}
func (a *LoggerAdapter) Error(msg string) {
a.thirdParty.LogMessage(msg, 2)
}
func useLogger(logger Logger) {
logger.Info("Application started")
logger.Error("An error occurred")
}
func main() {
adapter := &LoggerAdapter{
thirdParty: &ThirdPartyLogger{},
}
useLogger(adapter)
}
Use go-interfaces when you need to: