liveBook · Manning

Polymorphism

As explained in Go by Example

Polymorphism is the ability to treat instances of different types through a shared type or interface while preserving type-specific behavior.

Polymorphism lets code work through a common abstraction while different implementations provide type-specific behavior. In Go, it does not come from a class hierarchy: Go has a flat type structure and does not support classical inheritance. Runtime polymorphism is provided by interfaces. section 1.5.1

Struct embedding is composition, not inheritance. An embedding struct gains convenient access to embedded fields and methods, but the two struct types remain distinct and cannot be substituted for one another merely because one embeds the other. Treating embedding as inheritance is a common mistake; use interfaces when interchangeable runtime behavior is required. section 1.5.1

Figure 1.6 Inheritance creates a type hierarchy; embedding does not. In inheritance, you can pass parent (square) and child (circle) types interchangeably to a function that expects only the parent type. With embedding, the function can work only with the specified type. Embedding does not form a type hierarchy; it simply makes one type’s methods and data part of another type.
A cut out paper with a black line

AI-generated content may be incorrect.

Contrast a type hierarchy, where parent and child values are interchangeable, with embedding, which reuses members without creating that relationship. section 1.5.1

Polymorphism can also sit behind a concrete API. A concrete type can expose stable, common operations while delegating technology-specific work to internal interfaces. This keeps callers independent of implementation details without forcing every implementation to satisfy one large exported interface. section 10.1 section 10.6

The database driver pattern illustrates this design:

  • Call sql.Open with a driver name and a driver-specific connection string to obtain a *sql.DB. section 10.1
  • Use *sql.DB for shared work such as queries, connection pooling, and automatic retries rather than managing individual connections. section 10.1
  • Let database drivers communicate with SQLite, PostgreSQL, or other database technologies through sql/driver interfaces. section 10.1
  • Allow optional driver capabilities to be detected internally with type assertions, so the concrete API can gain features without breaking existing implementations. section 10.1 section 10.6

One API, multiple databases

The same sql.Open call shape can select SQLite with "sqlite" and "file:links.db", or PostgreSQL with "postgres" and "dbname=links sslmode=disable". Application code works with *sql.DB rather than database-specific connection behavior; the selected driver handles the underlying technology. section 10.1

Keep interfaces focused on observed shared behavior. Large interfaces make implementations harder to write and can break existing code when methods are added. Consumer-defined interfaces help express actual requirements, while a concrete type can evolve behind a uniform API. section 10.1 section 10.6

How Go by Example covers it

How deep it goes
Distinguishes embedding from inheritance, then explains concrete-type driver polymorphism and optional features via type assertions.
Examples
Demonstrates sql.Open with SQLite and PostgreSQL, mediated by *sql.DB and sql/driver interfaces.
What you can do after
Apply it via database/sql by using *sql.DB while drivers handle database-specific behavior.
How it connects
Connects polymorphism with interfaces, composition, struct embedding, type assertions, abstraction, and database drivers.

Also explained in

Compare how 9 books explain polymorphism →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go by Example. The section links point to the parts of the book it draws on.