Key Takeaways: Inheritance, Polymorphism, and Virtual Methods

Core Concepts

  1. Inheritance creates "is-a" relationships. A derived class automatically acquires all fields and methods of its base class, then adds its own specializations. Declare with class(TParent).

  2. virtual and override enable polymorphism. Mark base-class methods virtual to allow overriding. Use override in derived classes to replace the implementation. Without both keywords, you get method hiding (static binding) instead of polymorphism (dynamic dispatch).

  3. Polymorphism = one interface, many implementations. A base-class variable can hold any derived-class object. Virtual method calls dispatch to the correct implementation at runtime, based on the actual object type — not the declared variable type.

  4. Abstract methods define contracts without implementations. Declared virtual; abstract;, they force every concrete descendant to provide an implementation. Use them when there is no sensible default behavior.

  5. is tests type, as casts safely. The is operator checks whether an object belongs to a class (or its descendants). The as operator performs a checked downcast, raising EInvalidCast on failure. Prefer polymorphism over is/as chains.

  6. Every class descends from TObject. This root class provides Create, Free, Destroy, ClassName, InheritsFrom, and other utility methods inherited by all classes.

  7. Constructor chains build up; destructor chains tear down. Call inherited Create at the beginning of constructors. Call inherited Destroy at the end of destructors. The Destroy destructor must always be declared with override.

  8. The Liskov Substitution Principle (LSP) is your design compass. A derived class must be substitutable for its base class without breaking program correctness. If substitution would surprise calling code, the hierarchy is wrong.

Quick Reference

Keyword Where Purpose
virtual Base class Enables method to be overridden (adds VMT entry)
override Derived class Replaces a virtual method's implementation
abstract Base class Declares method with no body; derived must implement
reintroduce Derived class Intentionally hides (not overrides) a parent method
inherited Any method Calls the parent class's version of the method
is Expression Runtime type check (returns Boolean)
as Expression Safe downcast (raises exception on failure)

Common Pitfalls

  • Forgetting virtual in the base class — without it, override in the derived class will not compile, and you get static binding.
  • Forgetting override in the derived class — the method hides the parent's version; polymorphism does not work.
  • Not calling inherited in constructors — parent fields remain uninitialized, leading to subtle bugs.
  • Not calling inherited in destructors — parent resources are leaked.
  • Calling Destroy instead of Free — crashes if the reference is nil.
  • Overusing is/as — if you are writing long type-checking cascades, redesign with virtual methods instead.
  • Violating LSP — if a derived class's behavior would surprise code written for the base class, the inheritance relationship is wrong. Consider composition instead.

Design Guidelines

  • Prefer shallow hierarchies (2-3 levels) over deep ones (5+ levels).
  • Use protected for fields that derived classes need — not public, not private.
  • Make extension points virtual; make invariant behavior non-virtual.
  • Use abstract methods when there is no reasonable default.
  • Ask "is-a or has-a?" before reaching for inheritance. Composition is often the better choice.
  • Test LSP: Could code written for the base class use the derived class without surprises?