Key Takeaways: Inheritance, Polymorphism, and Virtual Methods
Core Concepts
-
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). -
virtualandoverrideenable polymorphism. Mark base-class methodsvirtualto allow overriding. Useoverridein derived classes to replace the implementation. Without both keywords, you get method hiding (static binding) instead of polymorphism (dynamic dispatch). -
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.
-
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. -
istests type,ascasts safely. Theisoperator checks whether an object belongs to a class (or its descendants). Theasoperator performs a checked downcast, raisingEInvalidCaston failure. Prefer polymorphism overis/aschains. -
Every class descends from
TObject. This root class providesCreate,Free,Destroy,ClassName,InheritsFrom, and other utility methods inherited by all classes. -
Constructor chains build up; destructor chains tear down. Call
inherited Createat the beginning of constructors. Callinherited Destroyat the end of destructors. TheDestroydestructor must always be declared withoverride. -
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
virtualin the base class — without it,overridein the derived class will not compile, and you get static binding. - Forgetting
overridein the derived class — the method hides the parent's version; polymorphism does not work. - Not calling
inheritedin constructors — parent fields remain uninitialized, leading to subtle bugs. - Not calling
inheritedin destructors — parent resources are leaked. - Calling
Destroyinstead ofFree— crashes if the reference isnil. - 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
protectedfor fields that derived classes need — notpublic, notprivate. - 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?