Skip to content

Inverted Control & Structural Governance

Fix the steps, let subclasses fill them in

Builds on Abstract classes and inheritance

Stage 3core2 min read

The refactored code uses the Template Method Design Pattern. This behavioral pattern puts the outline of an algorithm in a superclass. Subclasses can replace specific steps, but they cannot change the order of the steps.


1. How This Approach Solves the Issues

The separate, duplicated workflows are replaced by one abstract base class (a class that cannot be created on its own) that holds the template. The order of steps is now enforced in one place, and the shared code is reused.

IssueSolution with Template Method Pattern
Code DuplicationShared steps (rate limit checks, recipient validation, pre-send auditing) are in the base class, so the copies are gone.
Hardcoded BehaviorChannel-specific work (an HTML email body vs. a plain [SMS] text) is pushed down to the subclasses.
Lack of ExtensibilityTo add a new channel (e.g., PushNotification or SlackNotification), subclass the template and write its abstract methods.
Maintenance OverheadThe shared logic is in a single file (the abstract superclass). A new compliance rule or a change to rate limiting needs exactly one edit.

2. Key Architectural Steps of the Template Method Pattern

The refactored design has four kinds of methods. Together they fix the workflow and still let subclasses vary:

Template Method (final Method in Base Class)

The send() method runs the whole job. It lists the steps in a fixed order. Because it is marked final, subclasses cannot change the checks, reorder the steps or skip one.

Primitive Operations (abstract Methods)

The methods composeMessage() and sendMessage() are abstract. They are the steps that differ. A subclass such as EmailNotification or SMSNotification must write both, or the code will not compile.

Concrete Operations (private / concrete Methods)

Methods like rateLimitCheck(), validateRecipient(), and preSendAuditLog() are fully written in the base class. They are shared steps that every channel must run, and subclasses cannot change them.

Hooks (Optional Methods with Default Behavior)

The postSendAnalytics() method is a Hook. The base class gives it a default body. A subclass can keep that default (like EmailNotification) or override it with its own version (like SMSNotification).


3. Structural Breakdown

ComponentResponsibility
Abstract Class (NotificationSender)Declares the final template method to lock the step order and writes the shared logic.
Concrete Implementations (EmailNotification / SMSNotification)Write the abstract methods that compose the message and send it for their own channel.

4. Summary of the "Inverted Control" Architectural Model

The Template Method Pattern follows the Hollywood Principle: "Don't call us, we'll call you." The subclass does not drive the flow and call up into the parent. The parent drives. It runs the steps in order and calls down into the subclass only when it needs a channel-specific job, such as composing the message. This way a subclass can plug into the framework without breaking its flow.

In this topic

After code1 file

After/Main.java129 linesGitHub
package DesignPatterns.BehaviouralPatterns.TemplateMethodPattern.After;import java.util.*;// ============================================================================// 1. ABSTRACT BASE CLASS (The Skeletal Framework)// ============================================================================/** * Serves as the global governor for the execution pipeline. It enforces the overall * structural sequence of the algorithm, leaving individual specific variations * to be implemented by its subclasses. */abstract class NotificationSender {    /**     * THE TEMPLATE METHOD     * Notice the 'final' keyword. This is critical in an LLD interview because it     * prevents subclasses from overriding and breaking the master execution sequence.     */    public final void send(String to, String rawMessage) {        // Step 1: Invariant boilerplate orchestration execution        rateLimitCheck(to);        validateRecipient(to);        String formatted = formatMessage(rawMessage);        preSendAuditLog(to, formatted);        // Step 2: Abstract Hook Delegations (Variant mechanics managed by subclasses)        String composedMessage = composeMessage(formatted);        sendMessage(to, composedMessage);        // Step 3: Optional Hook Execution (Provides custom override intervention)        postSendAnalytics(to);    }    // --- INVARIANT STEPS (Shared Boilerplate Private Routines) ---    private void rateLimitCheck(String to) {        System.out.println("Checking rate limits for: " + to);    }    private void validateRecipient(String to) {        System.out.println("Validating recipient: " + to);    }    private String formatMessage(String message) {        return message.trim();    }    private void preSendAuditLog(String to, String formatted) {        System.out.println("Logging before send: " + formatted + " to " + to);    }    // --- ABSTRACT PRIMITIVE HOOKS (Subclasses MUST implement these) ---    protected abstract String composeMessage(String formattedMessage);    protected abstract void sendMessage(String to, String message);    // --- OPTIONAL HOOK (Provides baseline behavior, can be optionally overridden) ---    protected void postSendAnalytics(String to) {        System.out.println("Analytics updated for: " + to);    }}// ============================================================================// 2. CONCRETE IMPLEMENTATIONS (The Algorithmic Variants)// ============================================================================/** * Specializes strictly in text composition and network transport for Emails. * It is completely insulated from rate-limiting or workflow logging management. */class EmailNotification extends NotificationSender {    @Override    protected String composeMessage(String formattedMessage) {        return "<html><body><p>" + formattedMessage + "</p></body></html>";    }    @Override    protected void sendMessage(String to, String message) {        System.out.println(            "Sending EMAIL to " + to + " with content:\n" + message        );    }}/** * Specializes strictly in SMS mechanics. It also targets the optional hook * to override default analytics behavior with a platform-specific routine. */class SMSNotification extends NotificationSender {    @Override    protected String composeMessage(String formattedMessage) {        return "[SMS] " + formattedMessage;    }    @Override    protected void sendMessage(String to, String message) {        System.out.println(            "Sending SMS to " + to + " with message: " + message        );    }    /**     * OPTIONAL HOOK OVERRIDE     * Overriding this allows the subclass to inject specialized analytics behavior     * seamlessly without disrupting or modifying the core template execution path.     */    @Override    protected void postSendAnalytics(String to) {        System.out.println("Custom SMS analytics for: " + to);    }}// ============================================================================// 3. CLIENT CODE// ============================================================================class Main {    public static void main(String[] args) {        // Polymorphic declaration driven by the abstract template base type        NotificationSender emailSender = new EmailNotification();        emailSender.send("john@example.com", "Welcome to TUF+!");        System.out.println(" ");        NotificationSender smsSender = new SMSNotification();        smsSender.send("9876543210", "Your OTP is 4567.");    }}

Done with this topic?

How well do you know it?

PreviousLifecycle Management & State Transitions