Interfaces in Golang¶
In Go, an interface is a Type.
It defines a set of method signatures, but does not provide the implementation of those methods.
Interfaces are used to specify a "contract" that a concrete type must adhere to.
They allow you to write polymorphic code by decoupling the code that
uses an interface from the specific type
s that implement that interface.
They also allow to share function names across different types.
Basics of Go Interfaces¶
- An interface in Go is a Type definition that specifies a set
of method signatures (behavior).
- It doesn’t provide the implementation of these methods (i.e., the functions themselves).
- Instead, it defines a "contract" that any Type using the interface must fulfill.
- The "contract" is fulfilled by implementing the methods described by the interface.
- Think of an interface as a promise.
- A type that implements the interface promises to provide implementations of the methods defined by the interface.
- If a type implements all the methods an interface requires, it implicitly "satisfies" the interface.
Characteristics of Go Interfaces¶
-
Decoupling
- Interfaces help reduce dependencies between code components, making your code easier to manage and test.
-
Polymorphism
- They enable you to write functions that can accept any type that satisfies the interface.
Key Concepts and Examples¶
Defining and Implementing Interfaces¶
Basics of how to use interfaces in Go:
-
Implicit Implementations:
- An interface is implemented implicitly by a
type
whentype
has methods that match the interface's method set. - That means there's no need to declare that a
type
implements an interface, you only need to give thattype
the correct methods.
- An interface is implemented implicitly by a
-
You can explicitly declare that a type implements an interface once it "satisfies" the interface (see example of interface satisfaction).
- Declare a new variable of the Type of the interface.
- Initialize it with the type that implements the interface.
go var speaker MyInterface = MyType{}
- This is only possible if
MyType
already implicitly satisfiesMyInterface
by implementing its methods.
If the type has methods that match the interface's method set, then it implements the interface.
Define interfaces with type Name interface { ... }
.
package main
import "fmt"
// Define an interface
type Greeter interface {
Greet() string
}
// Define a type that implicitly implements the interface
type EnglishSpeaker struct{}
// Method that matches the Greeter interface signature
func (EnglishSpeaker) Greet() string {
return "Hello!"
}
// A function that takes the Greeter interface
func sayHello(g Greeter) {
fmt.Println(g.Greet())
}
func main() {
var speaker EnglishSpeaker
sayHello(speaker) // EnglishSpeaker implicitly implements Greeter
var speaker2 Greeter = EnglishSpeaker{} // Explicitly implements Greeter
sayHello(speaker2) // EnglishSpeaker implements Greeter
speaker.Speak()
}
Example of Interface Satisfaction:¶
// Define an interface
type Greeter interface {
Greet() string
}
// Define a type
type EnglishSpeaker struct {}
// Implement the Greeter interface implicitly
func (EnglishSpeaker) Greet() string {
return "Hello!"
}
// Demonstrate that EnglishSpeaker satisfies Greeter
var speaker Greeter = EnglishSpeaker{}
Different Ways of Implementing Interfaces¶
- Value Receivers
- Methods can be implemented on Value types (the object itself).
type MyStruct struct { ... } func (ms MyStruct) Method1() { ... }
- Methods can be implemented on Value types (the object itself).
- Pointer Receivers
- Methods can be implemented on Pointer Types (a pointer to the object).
func (ms *MyStruct) Method2() { ... }
- Methods can be implemented on Pointer Types (a pointer to the object).
- Combining Interfaces
- Interfaces can be composed of other interfaces.
type ReadWriter interface { Reader Writer }
- Interfaces can be composed of other interfaces.
- Type Switches:
- Interface types can be used in type switches to distinguish
between different types at runtime.
Usingfunc doSomething(i interface{}) { switch v := i.(type) { // .(type) can *only* be used in type switches case int: fmt.Println("Int", v) case string: fmt.Println("String", v) default: fmt.Println("Unknown Type") } }
.(type)
anywhere other than a type switch or type assertion will result in an error
- Interface types can be used in type switches to distinguish
between different types at runtime.
- Implementing Multiple Interfaces
- A type can implement multiple interfaces.
// Define Interface1 with a single method Method1 type Interface1 interface { Method1() } // Define Interface2 with a single method Method2 type Interface2 interface { Method2() } // MyType struct definition type MyType struct {} // Implement Method1 for MyType, satisfying Interface1 func (mt MyType) Method1() { fmt.Println("Method1 called") } // Implement Method2 for MyType, satisfying Interface2 func (mt MyType) Method2() { fmt.Println("Method2 called") } func main() { var mt MyType // Declare an Interface1 variable and assign mt to it var i1 Interface1 = mt i1.Method1() // Declare an Interface2 variable and assign mt to it var i2 Interface2 = mt i2.Method2() // MyType implements both Interface1 and Interface2 }
- A type can implement multiple interfaces.
- Anonymous Interfaces: You can declare interfaces inline for quick use,
especially in function arguments.
func foo(bar interface { Baz() }) { bar.Baz() }
- Empty Interface
interface{}
can hold values of any type.- It's useful for passing types that are unknown at compile time.
func printAnything(v interface{}) { fmt.Println(v) }
- Mocking in Tests: Interfaces are extremely useful in unit testing for mocking dependencies.
type MockLogger struct {} func (m MockLogger) Log(message string) { // Mock logging }
-
Decoupling: Interfaces help in writing decoupled code which enhances testability and maintainability.
type Logger interface { Log(message string) } type Service struct { logger Logger } // Service can use any Logger implementation // i.e., Service.Log()
-
Dependency Injection: Passing interfaces to functions allows for more flexible and testable code.
func process(logger Logger) { logger.Log("processing...") }
Interface Composition¶
- Interfaces can be "composed" of other interfaces using embedding.
- This can help make the code more modular and organized.
type Reader interface { Read(p []byte) (n int, err error) } type Closer interface { Close() error } // Compose interfaces type ReadCloser interface { Reader Closer }
- This can help make the code more modular and organized.
Type Assertions and Type Switches¶
- Type Assertions
- Allow you to retrieve the concrete type of an interface variable.
- Type Switches
- Enable you to perform actions based on the concrete type of an interface variable.
var i interface{} = "hello"
/* Example of type assertion */
s := i.(string) // Type assertion
fmt.Println(s)
/* Example of type switch */
switch v := i.(type) { // Type switch
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Int", v)
default:
fmt.Println("Unknown type")
}
Details about interfaces in Go¶
-
Method Signatures:
- An interface defines a list of method signatures without
specifying the actual code for those methods. - These method signatures describe the behavior that types
implementing the interface must provide.
- An interface defines a list of method signatures without
-
Implementation:
- A concrete type (a struct, for example) can implement an interface
by providing the necessary method implementations that match the
method signatures defined in the interface. - i.e., a type defines a function for itself that matches the function signatures defined in the interface.
- A concrete type (a struct, for example) can implement an interface
-
Implicit Implementation:
- Unlike some languages, Go doesn't require an explicit declaration that a type implements an interface.
- As long as a type provides the methods required by an interface, it is considered to implement that interface.
- E.g., the following code snippet defines a type
ConsoleWriter
that implements theWriter
interface.type Writer interface { Write([]byte) (int, error) } type ConsoleWriter struct {} // ConsoleWriter implicitly implements Writer func (cw ConsoleWriter) Write(data []byte) (int, error) { n, err := fmt.Println(string(data)) return n, err }
-
Polymorphism:
- Interfaces enable polymorphism in Go.
- You can write functions or methods that accept interfaces as parameters, making it possible to work with different types that implement the same interface without knowing their concrete types.
-
Empty Interface:
- In Go, the empty interface
interface{}
is an interface with no methods. - It can be used to represent any type, making it a powerful tool for working
with values of unknown types, similar to dynamic typing in other languages.
- In Go, the empty interface
Resources¶
- The Go Programming Language Specification
- For detailed information on how interfaces are represented and work internally.
- Effective Go
- Offers best practices and idiomatic ways to use interfaces in your Go programs.
- Go by Example
- Contains hands-on examples for various Go concepts including interfaces.
Text-based Example¶
You might have a function that creates and connects a REAL TCP socket
You also have another function that does the same, but PRETENDS to make the connection.
So, two behaviours, a real one, and a fake one.
When you run unit testing, we use the fake one.
When you run the real program, you use the real one.