Composition vs Inheritance & Interfaces as Contracts 🧱

One of the most famous pieces of advice in the Gang of Four design patterns book is:

β€œFavor object composition over class inheritance.”

Understanding why inheritance often leads to brittle systemsβ€”and why modern languages like Go and Rust completely omitted class inheritance in favor of composition and interfacesβ€”is a fundamental milestone in software design.


1. Inheritance: The β€œIs-A” Relationship

In classic Object-Oriented Programming, Inheritance lets a child class inherit methods and fields from a parent class:

          Vehicle (startEngine, stopEngine)
            β–²
            β”‚ (Inherits)
          Car (startEngine, stopEngine, openSunroof)
            β–²
            β”‚ (Inherits)
      ElectricCar (???) ── "Wait, electric cars don't have engines!"

The Problem: The Fragile Base Class & Gorilla-Banana Problem

As systems grow, deep inheritance trees become rigid and brittle:

  1. The Gorilla-Banana Problem: β€œYou wanted a banana, but what you got was a gorilla holding the banana and the entire jungle.” Subclasses inherit fields and behaviors they don’t want or need.
  2. Coupling: Changing a single method in the base class can silently break dozens of distant subclasses across the repository.

2. Composition: The β€œHas-A” Relationship (Lego Bricks)

Instead of saying what an object is (a child of another class), Composition builds complex objects by assembling smaller, focused components that the object has:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  ElectricCar                 β”‚
β”‚                                              β”‚
β”‚  [ ElectricMotor ]     [ BatteryPack ]       β”‚
β”‚  β”œβ”€β”€ start()           β”œβ”€β”€ charge()          β”‚
β”‚                                              β”‚
β”‚  [ GPSNavigation ]     [ SoundSystem ]       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
// Clean composition in Go (no inheritance!)
type Battery struct {
	CapacityKWh int
}
 
func (b *Battery) Charge() {
	println("Charging battery...")
}
 
type ElectricCar struct {
	Model   string
	Battery Battery // Has-A Battery!
}
 
func main() {
	tesla := ElectricCar{
		Model:   "Model Y",
		Battery: Battery{CapacityKWh: 75},
	}
	tesla.Battery.Charge()
}

Why Composition Wins in Production

  • Swapability: You can easily swap out Battery for a MockBattery in unit tests.
  • Flexibility: You can combine behaviors without being forced into an unnatural parent-child hierarchy.

3. Interfaces as Contracts

An Interface is simply a contract: it specifies what actions must be supported, without dictating how they are implemented.

Think of a standard electrical wall outlet:

  • The outlet defines an interface: two prongs delivering 120V AC.
  • A lamp, a laptop charger, and a toaster can all plug into the same outlet because they all adhere to the same interface.
// The Contract: Anything that knows how to Save an order is a StorageEngine!
type StorageEngine interface {
	Save(orderID string, data []byte) error
}
 
// 1. Production implementation: Saves to PostgreSQL
type PostgresStore struct { /* db connection */ }
func (p *PostgresStore) Save(orderID string, data []byte) error {
	// ... writes SQL insert ...
	return nil
}
 
// 2. Unit test implementation: Saves in-memory (No database needed!)
type FakeStore struct {
	SavedOrders map[string][]byte
}
func (f *FakeStore) Save(orderID string, data []byte) error {
	f.SavedOrders[orderID] = data
	return nil
}

The Power of Consumer-Driven Interfaces

In Go, interfaces are satisfied implicitly (structural typing). You don’t have to write class PostgresStore implements StorageEngine. If the struct has the Save() method, it satisfies the interface automatically!

This makes writing clean unit tests and replacing dependencies (like swapping an S3 client for local disk storage) trivial.


Across the Wiki