Go Interfaces - Simplifying Code and Boosting Flexibility
Go interfaces allow you to define object behavior using a set of method signatures, providing a flexible and scalable way to write code. They promote abstraction and code reuse, making it a key aspect of Go programming.
Here are some typical Go interface examples.
io.Readerandio.Writer: these interfaces provide a standard way to read from and write to sources such as files, network connections, or in-memory buffers.error: a built-in interface that represents an error condition in Go. Functions can return an error value, and clients can check whether an error occurred by checking if the error isnil.sort.Interface: this interface defines the methods that a type needs to implement to be sortable by the standard library's sorting functions.http.Handler: this interface is used by the net/http package to represent HTTP handlers. It defines a single method,ServeHTTP, which takes anhttp.ResponseWriterand anhttp.Requestas arguments.fmt.Stringer: this interface provides a standard way to represent an object as a string. Thefmtpackage'sPrintandPrintlnfunctions check if an object implements this interface and call its String method to get a string representation of the object.
These are just a few examples; you can create your own custom interfaces to meet the requirements of your particular use case.
Imagine you have a program that can send notifications via email, SMS, or push notifications. You can define an interface called Notifier that defines a method such as Notify, and then have each of your notification types implement that interface. This allows you to write a generic function that can send notifications using any type of notifier.
package main
import "fmt"
type Notifier interface {
Notify(message string)
}
type EmailNotifier struct {
email string
}
func (e EmailNotifier) Notify(message string) {
fmt.Printf("Sending email to %s: %s", e.email, message)
}
type SMSNotifier struct {
phoneNumber string
}
func (s SMSNotifier) Notify(message string) {
fmt.Printf("Sending SMS to %s: %s", s.phoneNumber, message)
}
type FCMNotifier struct {
deviceToken string
}
func (f FCMNotifier) Notify(message string) {
fmt.Printf("Sending fcm notification to %s: %s", f.deviceToken, message)
}
func main() {
var n Notifier
n = EmailNotifier{"john.doe@example.com"}
n.Notify("Important message")
n = SMSNotifier{"555-123-4567"}
n.Notify("Urgent message")
n = FCMNotifier{"some-fcm-device-token"}
n.Notify("Info message")
}
Suppose you have a program that logs data to various destinations, such as a file, the console, or a database. You can create a Logger interface that defines methods like Log and Close, and then have each of your logger types implement that interface. This enables you to create generic logging functions capable of writing to any type of logger.
package main
import (
"errors"
"fmt"
"log"
"os"
)
type Logger interface {
Log(message string)
Close()
}
// File logger
type FileLogger struct {
file *os.File
}
func (l *FileLogger) Log(message string) {
l.file.WriteString(message)
}
func (l *FileLogger) Close() {
l.file.Close()
}
func NewFileLogger(filename string) (*FileLogger, error) {
file, err := os.Create(filename)
if err != nil {
return nil, err
}
return &FileLogger{file}, nil
}
// Console logger
type ConsoleLogger struct{}
func (l *ConsoleLogger) Log(message string) {
fmt.Println(message)
}
func (l *ConsoleLogger) Close() {}
// Logger factory
func NewLogger(l string) (Logger, error) {
if l == "file" {
return NewFileLogger("/tmp/app.log")
}
if l == "console" {
return &ConsoleLogger{}, nil
}
return nil, errors.New("There are only file and console loggers available")
}
func main() {
logger, err := NewLogger("console")
if err != nil {
log.Println(err)
return
}
logger.Log("Starting application...")
logger.Log("Processing input...")
logger.Log("Shutting down application...")
logger.Close()
}