The idea of an abstract class
In some languages an abstract class is a half-finished template: you cannot create one directly, and it may declare methods without bodies that every concrete subclass must implement. The goal is a contract: all the types in a family promise to offer the same operations, each in its own way.
Go has no abstract classes. The contract is expressed by an interface, a set of method signatures with no bodies and no data:
type Animal interface {
speak()
}
What the interface guarantees
- You cannot make an
Animalby itself. Writing the literalAnimal{}is a compile error, because an interface has no fields to fill in. You can only store some concrete value in anAnimalvariable. A declaredvar a Animalwithout a value holdsnil, and calling a method on it panics. - The implementation must be complete. Assigning a value to an
Animalvariable compiles only if its type has every method of the interface. Otherwise the compiler reports the missing method.
For example, with var a Animal = Dog{} and a Dog that has no methods, the compiler reports that Dog does not implement Animal (missing method speak).
Implementing it
A concrete type implements the interface just by having the methods. There is no implements or extends keyword:
type Dog struct {}
func (this Dog) speak() {
fmt.Println("Woof")
}
The receiver in parentheses attaches speak to Dog. From that moment Dog satisfies Animal, even though the code never mentions the interface. Any other type with a matching speak() would satisfy it too.
Shared behaviour: no default method bodies
An abstract class in other languages can mix abstract methods with concrete ones that subclasses inherit. A Go interface cannot hold any code, so shared behaviour lives somewhere else. The usual place is a small struct with the common fields and methods, which implementing types embed:
type base struct{ name string }
func (b base) describe() string { return "I am " + b.name }
type Dog struct{ base }
func (d Dog) speak() { fmt.Println("Woof") }
Here Dog gets describe from the embedded struct and defines speak itself, so the interface carries the contract and embedding carries the shared code. Interfaces in Go are also usually small, often one or two methods, and are declared by the code that uses them.
Why not just use the struct?
The contract pays off in functions. A function that takes an Animal accepts every type that satisfies it, and it can be tested with a simple fake type that has only speak. A function that took a Dog would accept nothing else. The interface says what the function needs and nothing more, which keeps the types decoupled.
The lesson's exercise
The Animal interface and a mainFunction that stores a Dog{} in an Animal variable are already there. You supply the concrete type.
- Declare an empty
Dogstruct below the interface. The interface cannot be instantiated, so a concrete type must stand behind it. - Open a
speakmethod with aDogreceiver namedthis. This is what makesDogsatisfyAnimal. - Print
Woofwithfmt.Println.
type Animal interface {
speak()
}
type Dog struct {}
func (this Dog) speak() {
fmt.Println("Woof")
}
func mainFunction() {
var rex Animal = Dog{}
rex.speak()
}
It prints:
Woof
Common mistakes
- Using a different method name.
Speakandspeakare different names in Go, so aDogwithSpeak()does not satisfy an interface that asks forspeak(). - Using a different signature. The parameters and results must match the interface exactly.
- A pointer receiver with a value. If
speakwere declared on*Dog, thenvar rex Animal = Dog{}would not compile. Use&Dog{}or a value receiver. - Putting fields or bodies in the interface. Neither is allowed. Interfaces hold only method signatures.
Key takeaways
- Go uses interfaces where other languages use abstract classes.
- An interface cannot be instantiated, and implementing types must supply every method.
- Implementation is implicit: having the methods is enough.
- Shared code goes in embedded structs, not in the interface.