Code Quality Improvement Techniques Part 19: Child Lock (opens in new tab)
The "child lock" technique focuses on improving code robustness by restricting the scope of what child classes can override in an inheritance hierarchy. By moving away from broad, overridable functions that rely on manual super calls, developers can prevent common implementation errors and ensure that core logic remains intact across all subclasses. This approach shifts the responsibility of maintaining the execution flow to the parent class, making the codebase more predictable and easier to maintain.
Problems with Open Functions and Manual Super Calls
Providing an open function in a parent class that requires child classes to call super creates several risks:
- Missing
supercalls: If a developer forgets to callsuper.bind(), the essential logic in the parent class (such as updating headers or footers) is skipped, often leading to silent bugs that are difficult to track. - Implicit requirements: Relying on inline comments to tell developers they must override a function is brittle. If the method isn't
abstract, the compiler cannot enforce that the child class implements necessary logic. - Mismatched responsibilities: When a single function handles both shared logic and specific implementations, the responsibility of the code becomes blurred, making it easier for child classes to introduce side effects or incorrect behavior.
Implementing the "Child Lock" with Template Methods
To resolve these issues, the post recommends a pattern often referred to as the Template Method pattern:
- Seal the execution flow: Remove the
openmodifier from the primary entry point (e.g., thebindmethod). This prevents child classes from changing the overall sequence of operations. - Separate concerns: Move the customizable portion of the logic into a new
protected abstractfunction. - Enforced implementation: Because the new function is
abstract, the compiler forces every child class to provide an implementation, ensuring that specific logic is never accidentally omitted. - Guaranteed execution: The parent class calls the abstract method from within its non-overridable method, ensuring that shared logic (like UI updates) always runs regardless of how the child is implemented.
Refining Overridability and Language Considerations
Designing for inheritance requires careful control over how child classes interact with parent logic:
- Avoid "super" dependency: Generally, if a child class must explicitly call a parent function to work correctly, the inheritance structure is too loose. Exceptions are usually limited to lifecycle methods like
onCreatein Android or constructors/destructors. - C++ Private Virtuals: In C++, developers can use
private virtualfunctions. These allow a parent class to define a rigid flow in a public method while still allowing subclasses to provide specific implementations for the private virtual components, even though the child cannot call those functions directly.
To ensure long-term code quality, the range of overridability should be limited as much as possible. By narrowing the interface between parent and child classes, you create a more rigid "contract" that prevents accidental bugs and clarifies the intent of the code.