Overview

8 Elements of Object-Oriented Programming

This chapter surveys the essential building blocks of object-oriented programming and how to apply them effectively in TypeScript. It frames interfaces as explicit contracts, contrasts them with abstract classes, and emphasizes designing for loose coupling and clarity. It also introduces the guiding heuristics behind the main OOP relationships—when a type “is-a” another, when it “has-a” another, and when it “includes” behavior—and closes by highlighting pragmatic alternatives that can yield simpler or more reusable designs in some cases.

Interfaces specify contracts independent of implementation or state, can be extended or combined, and are ideal targets for “coding against interfaces” and dependency injection. Inheritance is presented as appropriate for true is-a hierarchies or when most state/behavior is shared but small parts vary (for example, parameterizing eval in an expression hierarchy with abstract methods), with cautions against deep or leaky hierarchies. Composition is recommended as a safer default when the relationship is has-a, improving encapsulation and clarity; the adapter pattern exemplifies composition by wrapping an incompatible type and translating to the expected interface without modifying either side.

To extend behavior, the chapter first shows composition-based delegation (wrapping a shared behavior class) and then mix-ins, noting that TypeScript enables mix-ins via intersection types and runtime extension, and that they work best for cross-cutting concerns (e.g., caching or persistence) while carrying semantic nuance versus inheritance. Finally, it surveys alternatives to pure OOP: sum types (variants) to unify disparate types without common bases, functional programming to pass behavior as functions (simplifying patterns like strategy) and leverage closures for state, and generic programming to abstract and reuse code. The overarching takeaway is to choose the approach—interface-driven design, inheritance, composition, mix-ins, or non-OOP techniques—that yields the clearest, safest, and most decoupled solution.

All animals eat. We can play with pets (they still need to eat though). Cats also meow. They still play and eat.
Expression hierarchy with BinaryExpression as parent and SumExpression and MulExpression as children.
All shapes have an id. A circle is a shape, so it inherits the id. A circle has a point, which defines it’s center.
We have an IExpected interface and an Adaptee actual implementation which are incompatible. The Adapter makes them compatible by providing an implementation of IExpected and handling translation between what IExpected declares and what the actual Adaptee provides.
Extended animal hierarchy with WildAnimal and Wolf. Wild animals can roam(), a wolf can hunt with its track(), stalk(), and pounce() methods.
The Hunter type is the parent of Wolf and Tiger and provides hunting behavior.
Cat, Wolf, and Tiger wrap an instance of HunterBehavior and implement the IHunter interface. They forward all calls to the wrapped object. HunterBehavior provides an implementation of IHunter which all animals implementing IHunter can use as a component. Hunting behavior is no longer part of the Animal hierarchy.
Cat, Wolf, and Tiger mix in HunterBehavior. This removes a bunch of boilerplate: the classes no longer need to wrap a HunterBehavior object and forward calls, rather they can simply include the behavior.
Object-oriented strategy pattern. Different versions of an algorithm are implemented in ConcreteStrategy1 and ConcreteStrategy2.
Functional strategy pattern. Different versions of an algorithm are implemented as functions.

FAQ

What’s the key difference between an interface and an abstract class?An interface specifies a contract (method names, parameters, return types) and has no state, while an abstract class can include state and concrete methods in addition to abstract members. Interfaces describe “what” is required; abstract classes can also provide part of the “how.”
When should I prefer an interface over an abstract class?Prefer an interface when you’re expressing a pure contract for consumers to depend on (for example, ILogger with log()). It clearly communicates intent, avoids unnecessary inheritance, and allows a class to implement multiple contracts.
Can interfaces be extended or combined?Yes. Interfaces can extend other interfaces to add members (e.g., IExtendedLogger extends ILogger) and can also extend multiple interfaces to compose capabilities (e.g., ISpeakerWithVolumeControl extends both ISpeaker and IVolumeControl).
How do I decide between inheritance and composition?Use the is-a rule for inheritance (e.g., Cat is an Animal) and the has-a rule for composition (e.g., Circle has a Point center). If there’s no clear is-a relationship, prefer composition.
What does “parameterizing behavior” with inheritance mean?It means factoring shared state/structure into an abstract base and letting derived classes provide the variable behavior. Example: BinaryExpression holds operands while SumExpression and MulExpression override eval() differently.
Why avoid deep inheritance hierarchies?They’re hard to understand and maintain, and behavior may become surprising when upcasting. Keep parents abstract, children concrete, and consider marking leaf classes as non-inheritable (e.g., final/sealed) where supported.
What is the adapter pattern and when should I use it?An adapter wraps an object and translates its API to a required interface without modifying the original type. Use it to integrate external libraries or legacy code (e.g., CircleAdapter implements a library’s ICircle while delegating to your Circle).
What are mix-ins and how do intersection types help in TypeScript?Mix-ins add reusable behavior to classes outside the main inheritance chain (an “includes” relationship). In TypeScript, intersection types (e.g., TypeA & TypeB) and helper functions like extend() can combine behaviors and reduce boilerplate for cross-cutting concerns.
What alternatives to pure OOP does the chapter highlight?- Sum types (variants) to avoid mandatory shared interfaces and enable visitor-like flows without coupling widgets to visitors - Functional programming: pass functions instead of strategy classes; closures capture state - Generic programming: abstract over types to maximize reuse with minimal coupling
How do “coding against interfaces” and dependency injection reduce coupling?By depending on contracts, consumers don’t care about concrete implementations. A DI container maps interfaces to implementations, simplifying wiring, enabling easy swaps, and making unit testing straightforward with stubs/mocks.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programming with Types ebook for free