Chapter 04: Interfaces
1 The Implicit Contract (Duck Typing)
In strict languages (Java, C++), you must explicitly sign the contract. class MyStore implements BookRepository.
In Go, the contract is implicit. This is called "Duck Typing".
"If it walks like a duck and quacks like a duck, it is a duck."
If your struct has the methods GetAll() and GetByID(), Go automatically considers it a BookRepository.
Anatomy of an Interface
type BookRepository interface {
GetAll() ([]Book, error)
GetByID(id string) (Book, error)
}type: New Type Definition.BookRepository: The name (Contract Name).interface: The Kind. It contains only Method Signatures, no code.GetByID: Method Name.(id string): Usage requirements (Input).(Book, error): Expected result (Output).
Where to Define Interfaces?
Beginners often define the interface in the Implementation package (e.g., inside store/). Idiomatic Go: Define the interface where it is USED (e.g., in main.go or api/), not where it is implemented.
- Java: Producer defines the interface.
- Go: Consumer defines the interface. (Note: In this project, we keep it in
store/for simplicity, but strictly speaking,mainshould define what it needs).
TIP
🛡️ Interview Defense: "The Legend"
Interviewer: "Why did you use an interface for BookRepository?"
You: "I used an interface to decouple the business logic from the specific database implementation. This allows me to:
- Swap Infrastructure: Switch from InMemory to PostgreSQL without rewriting the application logic.
- Testability: Easily mock the database in unit tests to verify logic without a running DB connection.
- Flexibility: Adhere to the Open/Closed Principle—open for extension (new stores), closed for modification (existing logic)."
2 Dependency Injection (DI)
Big phrase, simple concept. Don't build your tools inside your house. Buy them and bring them in.
Bad (Tight Coupling):
func NewHandler() *Handler {
repo := PostgresStore{} // Hardcoded! Can't switch to Memory.
return &Handler{repo: repo}
}Good (Dependency Injection):
func NewHandler(repo BookRepository) *Handler {
return &Handler{repo: repo} // Flexible! Accepts any repo.
}Now main.go decides which tool to use.
🎓 Knowledge Check: Why do we use Interfaces for Dependency Injection?
Answer: To decouple our code. If Handler depends on an Interface, we can swap the real database for a "Fake Database" (Mock) during testing, or switch from Postgres to MySQL without rewriting the Handler.
3 The Visual Signal (The Power Socket)
Concept: Decoupling implementation from usage. Signal: A Universal Power Socket. The Lamp doesn't care if the power comes from a wall, a battery, or a generator, as long as the plug fits.
