Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

Application of Template Method Pattern in C Language (Including Linux Kernel Examples)

1. Definition and Core Value of Template Method Pattern

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm (the template method) and defers some steps to subclasses (or concrete implementations). The template method fixes the overall process of the algorithm while allowing the implementation of specific steps to vary flexibly, thus supporting diversity in step implementation while ensuring process consistency.

Significance: When multiple algorithms or processes share the same overall framework but differ in the implementation details of certain steps (e.g., “Initialization → Processing → Cleanup”), directly repeating the implementation of the framework can lead to code redundancy and make it difficult to modify the framework, requiring synchronization of all implementations. The Template Method Pattern extracts the common framework as a template and abstracts the variable steps as overridable interfaces, achieving “framework reuse and detail customization”.

Problems Solved:

  • • Multiple similar processes repeat the implementation of a common framework, leading to code redundancy and high maintenance costs;
  • • Modifications to the common framework require synchronization of all related implementations, which can easily introduce errors;
  • • The calling order of process steps is scattered across various implementations, making it difficult to ensure consistency.

2. Core Idea of Implementing Template Method Pattern in C Language

C language implements the Template Method Pattern through ** function pointers + struct composition:

  1. 1. Define the template framework (Template): The struct contains the template method (a function with a fixed process) and function pointers to abstract steps (steps that need concrete implementations);
  2. 2. Implement the template method: In the template method, call the function pointers of the abstract steps in a fixed order to form a complete algorithm process;
  3. 3. Derive concrete implementations: By initializing the function pointers of the struct, provide concrete implementations for the abstract steps, reusing the process framework of the template method.

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

3. 5 Examples

Example 1: Basic Template Method (Data Processing Flow)

Simulate the fixed process of “Read Data → Process Data → Save Result”, where the “Process Data” step can be customized.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 1. Template Framework: Data Processor
typedef struct {
    // Abstract Steps (need concrete implementation)
    void (*read_data)(struct DataProcessor* self, const char* filename);
    void (*process_data)(struct DataProcessor* self);
    void (*save_result)(struct DataProcessor* self, const char* filename);
    // Template Method (fixed process)
    void (*execute)(struct DataProcessor* self, const char* infile, const char* outfile);
    // Data Buffer
    char data[1024];
} DataProcessor;

// Template Method: Fixed Process
static void data_processor_execute(DataProcessor* self, const char* infile, const char* outfile) {
    printf("=== Starting data processing ===\n");
    self->read_data(self, infile);    // Step 1: Read Data
    self->process_data(self);         // Step 2: Process Data (variable)
    self->save_result(self, outfile); // Step 3: Save Result
    printf("=== Processing completed ===\n");
}

// Initialize Template Framework (bind template method)
void init_data_processor(DataProcessor* self) {
    self->execute = data_processor_execute;
    // Abstract steps initialized to NULL (need concrete implementation to fill)
    self->read_data = NULL;
    self->process_data = NULL;
    self->save_result = NULL;
}

// 2. Concrete Implementation 1: Upper Case Converter Processor (Processing step is string upper case)
static void upper_read(DataProcessor* self, const char* filename) {
    // Simulate reading file
    snprintf(self->data, sizeof(self->data), "hello from %s", filename);
    printf("Read data: %s\n", self->data);
}

static void upper_process(DataProcessor* self) {
    // Processing step: convert to upper case
    for (int i = 0; self->data[i]; i++) {
        if (self->data[i] >= 'a' && self->data[i] <= 'z') {
            self->data[i] -= 32;
        }
    }
    printf("Processed data (upper): %s\n", self->data);
}

static void upper_save(DataProcessor* self, const char* filename) {
    // Simulate saving file
    printf("Saved result to %s: %s\n", filename, self->data);
}

// Create Upper Case Converter Processor
DataProcessor* create_upper_processor() {
    DataProcessor* proc = malloc(sizeof(DataProcessor));
    init_data_processor(proc);
    // Bind concrete steps
    proc->read_data = upper_read;
    proc->process_data = upper_process;
    proc->save_result = upper_save;
    return proc;
}

// Concrete Implementation 2: Reverse String Processor (Processing step is string reverse)
static void reverse_read(DataProcessor* self, const char* filename) {
    // Reuse similar reading logic
    snprintf(self->data, sizeof(self->data), "hello from %s", filename);
    printf("Read data: %s\n", self->data);
}

static void reverse_process(DataProcessor* self) {
    // Processing step: reverse string
    int len = strlen(self->data);
    for (int i = 0; i < len/2; i++) {
        char temp = self->data[i];
        self->data[i] = self->data[len - i - 1];
        self->data[len - i - 1] = temp;
    }
    printf("Processed data (reverse): %s\n", self->data);
}

static void reverse_save(DataProcessor* self, const char* filename) {
    // Reuse similar saving logic
    printf("Saved result to %s: %s\n", filename, self->data);
}

// Create Reverse Processor
DataProcessor* create_reverse_processor() {
    DataProcessor* proc = malloc(sizeof(DataProcessor));
    init_data_processor(proc);
    proc->read_data = reverse_read;
    proc->process_data = reverse_process;
    proc->save_result = reverse_save;
    return proc;
}

// Client Usage
int main() {
    // Use Upper Case Converter Processor
    printf("--- Upper Processor ---\n");
    DataProcessor* upper = create_upper_processor();
    upper->execute(upper, "input.txt", "upper_out.txt");

    // Use Reverse Processor
    printf("\n--- Reverse Processor ---\n");
    DataProcessor* reverse = create_reverse_processor();
    reverse->execute(reverse, "input.txt", "reverse_out.txt");

    free(upper);
    free(reverse);
    return 0;
}

The execution result of the above code is as follows

--- Upper Processor ---
=== Starting data processing ===
Read data: hello from input.txt
Processed data (upper): HELLO FROM INPUT.TXT
Saved result to upper_out.txt: HELLO FROM INPUT.TXT
=== Processing completed ===

--- Reverse Processor ---
=== Starting data processing ===
Read data: hello from input.txt
Processed data (reverse): txt.tupni morf olleh
Saved result to reverse_out.txt: txt.tupni morf olleh
=== Processing completed ===

The UML diagram is as follows

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

That is: <span>DataProcessor</span> defines the fixed process of “Read → Process → Save” (template method <span>execute</span>), while <span>upper_process</span> and <span>reverse_process</span> implement different “processing” logic, reusing the same process framework, in line with the core idea of the template method “fixed skeleton, customized details”.

Example 2: Device Initialization Template (Hardware Startup Process)

Simulate the fixed startup process of hardware devices “Power On → Initialize Registers → Self-Test → Enable”, where the “Initialize Registers” step varies by device.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

// 1. Template Framework: Device Bootstrapper
typedef struct {
    // Abstract Steps
    void (*power_on)(struct DeviceBootstrapper* self);
    void (*init_registers)(struct DeviceBootstrapper* self); // Device-specific step
    bool (*self_test)(struct DeviceBootstrapper* self);
    void (*enable)(struct DeviceBootstrapper* self);
    // Template Method: Fixed Startup Process
    bool (*boot)(struct DeviceBootstrapper* self);
    // Device Information
    const char* name;
    uint32_t reg_base; // Register base address
} DeviceBootstrapper;

// Template Method: Fixed Startup Process
static bool device_boot(DeviceBootstrapper* self) {
    printf("=== Booting %s ===\n", self->name);
    self->power_on(self);
    self->init_registers(self);
    if (!self->self_test(self)) {
        printf("Boot failed: self-test failed\n");
        return false;
    }
    self->enable(self);
    printf("=== %s booted successfully ===\n", self->name);
    return true;
}

// Initialize Template Framework
void init_device_bootstrapper(DeviceBootstrapper* self, const char* name, uint32_t reg_base) {
    self->name = name;
    self->reg_base = reg_base;
    self->boot = device_boot;
    // Abstract steps initialized to NULL
    self->power_on = NULL;
    self->init_registers = NULL;
    self->self_test = NULL;
    self->enable = NULL;
}

// 2. Concrete Implementation 1: UART Device Startup
static void uart_power_on(DeviceBootstrapper* self) {
    printf("[%s] Powering on UART\n", self->name);
}

static void uart_init_registers(DeviceBootstrapper* self) {
    // UART-specific register initialization
    printf("[%s] Initializing UART registers at 0x%08X\n", self->name, self->reg_base);
    printf("  - Setting baud rate to 115200\n");
    printf("  - Enabling TX/RX pins\n");
}

static bool uart_self_test(DeviceBootstrapper* self) {
    printf("[%s] Performing UART loopback test... OK\n", self->name);
    return true;
}

static void uart_enable(DeviceBootstrapper* self) {
    printf("[%s] Enabling UART controller\n", self->name);
}

// Create UART Bootstrapper
DeviceBootstrapper* create_uart_bootstrapper() {
    DeviceBootstrapper* boot = malloc(sizeof(DeviceBootstrapper));
    init_device_bootstrapper(boot, "UART0", 0x10000000);
    boot->power_on = uart_power_on;
    boot->init_registers = uart_init_registers;
    boot->self_test = uart_self_test;
    boot->enable = uart_enable;
    return boot;
}

// Concrete Implementation 2: SPI Device Startup
static void spi_power_on(DeviceBootstrapper* self) {
    printf("[%s] Powering on SPI\n", self->name);
}

static void spi_init_registers(DeviceBootstrapper* self) {
    // SPI-specific register initialization
    printf("[%s] Initializing SPI registers at 0x%08X\n", self->name, self->reg_base);
    printf("  - Setting clock frequency to 1MHz\n");
    printf("  - Configuring as master mode\n");
}

static bool spi_self_test(DeviceBootstrapper* self) {
    printf("[%s] Performing SPI chip select test... OK\n", self->name);
    return true;
}

static void spi_enable(DeviceBootstrapper* self) {
    printf("[%s] Enabling SPI controller\n", self->name);
}

// Create SPI Bootstrapper
DeviceBootstrapper* create_spi_bootstrapper() {
    DeviceBootstrapper* boot = malloc(sizeof(DeviceBootstrapper));
    init_device_bootstrapper(boot, "SPI0", 0x10001000);
    boot->power_on = spi_power_on;
    boot->init_registers = spi_init_registers;
    boot->self_test = spi_self_test;
    boot->enable = spi_enable;
    return boot;
}

// Client Usage
int main() {
    // Boot UART Device
    printf("--- UART Boot ---\n");
    DeviceBootstrapper* uart = create_uart_bootstrapper();
    uart->boot(uart);

    // Boot SPI Device
    printf("\n--- SPI Boot ---\n");
    DeviceBootstrapper* spi = create_spi_bootstrapper();
    spi->boot(spi);

    free(uart);
    free(spi);
    return 0;
}

The execution result of the above code is as follows

--- UART Boot ---
=== Booting UART0 ===
[UART0] Powering on UART
[UART0] Initializing UART registers at 0x10000000
  - Setting baud rate to 115200
  - Enabling TX/RX pins
[UART0] Performing UART loopback test... OK
[UART0] Enabling UART controller
=== UART0 booted successfully ===

--- SPI Boot ---
=== Booting SPI0 ===
[SPI0] Powering on SPI
[SPI0] Initializing SPI registers at 0x10001000
  - Setting clock frequency to 1MHz
  - Configuring as master mode
[SPI0] Performing SPI chip select test... OK
[SPI0] Enabling SPI controller
=== SPI0 booted successfully ===

The UML diagram is as follows

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

That is: All devices follow the fixed startup process of “Power On → Initialize Registers → Self-Test → Enable” (template method <span>boot</span>), but the <span>init_registers</span> step varies by device type (UART/SPI), reflecting a typical application of the template method in hardware initialization.

Example 3: Network Request Template (HTTP/HTTPS Request Process)

Simulate the fixed process of network requests “Establish Connection → Send Request → Receive Response → Close Connection”, where the “Send Request” and “Receive Response” steps vary by protocol (HTTP/HTTPS).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// 1. Template Framework: Network Requester
typedef struct {
    // Abstract Steps
    bool (*connect)(struct NetworkRequester* self, const char* host, int port);
    bool (*send_request)(struct NetworkRequester* self, const char* path); // Protocol-related
    bool (*recv_response)(struct NetworkRequester* self, const char* path,char* buffer, int len); // Protocol-related
    void (*disconnect)(struct NetworkRequester* self);
    // Template Method: Fixed Request Process
    bool (*request)(struct NetworkRequester* self, const char* host, int port, const char* path, char* resp, int len);
    // Connection Status
    bool is_connected;
} NetworkRequester;

// Template Method: Fixed Request Process
static bool network_request(NetworkRequester* self, const char* host, int port, const char* path, char* resp, int len) {
    printf("=== Starting network request ===\n");
    if (!self->connect(self, host, port)) {
        printf("Request failed: connect failed\n");
        return false;
    }
    if (!self->send_request(self, path)) {
        self->disconnect(self);
        printf("Request failed: send failed\n");
        return false;
    }
    if (!self->recv_response(self, path,resp, len)) {
        self->disconnect(self);
        printf("Request failed: recv failed\n");
        return false;
    }
    self->disconnect(self);
    printf("=== Request completed ===\n");
    return true;
}

// Initialize Template Framework
void init_network_requester(NetworkRequester* self) {
    self->is_connected = false;
    self->request = network_request;
    // Abstract steps initialized to NULL
    self->connect = NULL;
    self->send_request = NULL;
    self->recv_response = NULL;
    self->disconnect = NULL;
}

// 2. Concrete Implementation 1: HTTP Request
static bool http_connect(NetworkRequester* self, const char* host, int port) {
    int used_port = (port == 0) ? 80 : port;
    printf("HTTP connecting to %s:%d\n", host, used_port);
    self->is_connected = true;
    return true;
}

static bool http_send_request(NetworkRequester* self, const char* path) {
    if (!self->is_connected) return false;
    printf("HTTP sending: GET %s HTTP/1.1\n", path);
    return true;
}

static bool http_recv_response(NetworkRequester* self, const char* path,char* buffer, int len) {
    if (!self->is_connected) return false;
    snprintf(buffer, len, "HTTP/1.1 200 OK\nContent: Hello from %s", path);
    printf("HTTP received: %s\n", buffer);
    return true;
}

static void http_disconnect(NetworkRequester* self) {
    if (self->is_connected) {
        printf("HTTP disconnecting\n");
        self->is_connected = false;
    }
}

// Create HTTP Requester
NetworkRequester* create_http_requester() {
    NetworkRequester* req = malloc(sizeof(NetworkRequester));
    init_network_requester(req);
    req->connect = http_connect;
    req->send_request = http_send_request;
    req->recv_response = http_recv_response;
    req->disconnect = http_disconnect;
    return req;
}

// Concrete Implementation 2: HTTPS Request
static bool https_connect(NetworkRequester* self, const char* host, int port) {
    int used_port = (port == 0) ? 443 : port;
    printf("HTTPS connecting to %s:%d (TLS handshake)\n", host, used_port);
    self->is_connected = true;
    return true;
}

static bool https_send_request(NetworkRequester* self, const char* path) {
    if (!self->is_connected) return false;
    printf("HTTPS sending (encrypted): GET %s HTTP/1.1\n", path);
    return true;
}

static bool https_recv_response(NetworkRequester* self, const char* path,char* buffer, int len) {
    if (!self->is_connected) return false;
    snprintf(buffer, len, "HTTPS/1.1 200 OK\nEncrypted Content: Hello from %s", path);
    printf("HTTPS received (decrypted): %s\n", buffer);
    return true;
}

static void https_disconnect(NetworkRequester* self) {
    if (self->is_connected) {
        printf("HTTPS disconnecting (TLS close)\n");
        self->is_connected = false;
    }
}

// Create HTTPS Requester
NetworkRequester* create_https_requester() {
    NetworkRequester* req = malloc(sizeof(NetworkRequester));
    init_network_requester(req);
    req->connect = https_connect;
    req->send_request = https_send_request;
    req->recv_response = https_recv_response;
    req->disconnect = https_disconnect;
    return req;
}

// Client Usage
int main() {
    char resp[512];

    // Send HTTP Request
    printf("--- HTTP Request ---\n");
    NetworkRequester* http = create_http_requester();
    http->request(http, "example.com", 0, "/index.html", resp, sizeof(resp));

    // Send HTTPS Request
    printf("\n--- HTTPS Request ---\n");
    NetworkRequester* https = create_https_requester();
    https->request(https, "example.com", 0, "/secure.html", resp, sizeof(resp));

    free(http);
    free(https);
    return 0;
}

The execution result of the above code is as follows

--- HTTP Request ---
=== Starting network request ===
HTTP connecting to example.com:80
HTTP sending: GET /index.html HTTP/1.1
HTTP received: HTTP/1.1 200 OK
Content: Hello from /index.html
HTTP disconnecting
=== Request completed ===

--- HTTPS Request ---
=== Starting network request ===
HTTPS connecting to example.com:443 (TLS handshake)
HTTPS sending (encrypted): GET /secure.html HTTP/1.1
HTTPS received (decrypted): HTTPS/1.1 200 OK
Encrypted Content: Hello from /secure.html
HTTPS disconnecting (TLS close)
=== Request completed ===

The UML diagram is as follows

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

That is: HTTP and HTTPS requests share the process framework of “Connect → Send → Receive → Disconnect” (template method <span>request</span>), but the <span>send_request</span> and <span>recv_response</span> steps differ due to encryption requirements, reflecting the encapsulation of protocol details by the template method.

Example 4: Test Case Template (Unit Test Process)

Simulate the fixed process of unit testing “Initialize → Execute Test → Cleanup → Verify Result”, where the “Execute Test” step varies by test object.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 1. Template Framework: Test Case
typedef struct {
    // Abstract Steps
    void (*setup)(struct TestCase* self);    // Initialize
    void (*run)(struct TestCase* self);      // Execute Test (variable)
    void (*teardown)(struct TestCase* self); // Cleanup
    bool (*verify)(struct TestCase* self);   // Verify Result
    // Template Method: Fixed Test Process
    bool (*execute)(struct TestCase* self);
    // Test Status
    const char* name;
    bool passed;
} TestCase;

// Template Method: Fixed Test Process
static bool testcase_execute(TestCase* self) {
    printf("=== Running test: %s ===\n", self->name);
    self->setup(self);
    self->run(self);
    self->teardown(self);
    self->passed = self->verify(self);
    printf("Test %s: %s\n", self->name, self->passed ? "PASSED" : "FAILED");
    printf("=== Test completed ===\n");
    return self->passed;
}

// Initialize Template Framework
void init_test_case(TestCase* self, const char* name) {
    self->name = name;
    self->passed = false;
    self->execute = testcase_execute;
    // Abstract steps initialized to NULL
    self->setup = NULL;
    self->run = NULL;
    self->teardown = NULL;
    self->verify = NULL;
}

// 2. Concrete Implementation 1: Addition Function Test
static int add(int a, int b) { return a + b; } // Function to be tested

static void add_setup(TestCase* self) {
    (void)self;
    printf("Setting up add test\n");
}

static void add_run(TestCase* self) {
    (void)self;
    printf("Running add test: 2 + 3 = %d\n", add(2, 3));
}

static void add_teardown(TestCase* self) {
    (void)self;
    printf("Tearing down add test\n");
}

static bool add_verify(TestCase* self) {
    (void)self;
    return add(2, 3) == 5; // Verify Result
}

// Create Addition Test Case
TestCase* create_add_test() {
    TestCase* test = malloc(sizeof(TestCase));
    init_test_case(test, "AddTest");
    test->setup = add_setup;
    test->run = add_run;
    test->teardown = add_teardown;
    test->verify = add_verify;
    return test;
}

// 具体实现2:字符串反转测试
static void reverse_str(char* str) {
    int len = 0;
    while (str[len]) len++;
    for (int i = 0; i < len/2; i++) {
        char temp = str[i];
        str[i] = str[len - i - 1];
        str[len - i - 1] = temp;
    }
} // Function to be tested

static char test_str[32]; // Test Data

static void reverse_setup(TestCase* self) {
    (void)self;
    printf("Setting up reverse test\n");
    strcpy(test_str, "hello");
}

static void reverse_run(TestCase* self) {
    (void)self;
    printf("Original string: %s\n", test_str);
    reverse_str(test_str);
    printf("Reversed string: %s\n", test_str);
}

static void reverse_teardown(TestCase* self) {
    (void)self;
    printf("Tearing down reverse test\n");
}

static bool reverse_verify(TestCase* self) {
    (void)self;
    return strcmp(test_str, "olleh") == 0; // Verify Result
}

// Create Reverse Test Case
TestCase* create_reverse_test() {
    TestCase* test = malloc(sizeof(TestCase));
    init_test_case(test, "ReverseTest");
    test->setup = reverse_setup;
    test->run = reverse_run;
    test->teardown = reverse_teardown;
    test->verify = reverse_verify;
    return test;
}

// Client Usage
int main() {
    // Execute Addition Test
    printf("--- Add Test ---\n");
    TestCase* add_test = create_add_test();
    add_test->execute(add_test);

    // Execute Reverse Test
    printf("\n--- Reverse Test ---\n");
    TestCase* reverse_test = create_reverse_test();
    reverse_test->execute(reverse_test);

    free(add_test);
    free(reverse_test);
    return 0;
}

The execution result of the above code is as follows

--- Add Test ---
=== Running test: AddTest ===
Setting up add test
Running add test: 2 + 3 = 5
Tearing down add test
Test AddTest: PASSED
=== Test completed ===

--- Reverse Test ---
=== Running test: ReverseTest ===
Setting up reverse test
Original string: hello
Reversed string: olleh
Tearing down reverse test
Test ReverseTest: PASSED
=== Test completed ===

The UML diagram is as follows

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

That is: All test cases follow the fixed process of “Initialize → Execute → Cleanup → Verify” (template method <span>execute</span>), while the <span>run</span> step varies by test object (addition / string reversal), in line with the design logic of unit testing frameworks (like JUnit’s <span>@Before</span>/<span>@Test</span>/<span>@After</span>).

Example 5: Kernel Style Template (Module Loading Process)

Simulate the fixed process of Linux kernel modules “Load → Initialize → Run → Unload”, where the “Initialize” and “Run” steps vary by module functionality.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// 1. Template Framework: Kernel Module
typedef struct {
    // Abstract Steps (Kernel Module Required Interfaces)
    int (*init)(struct KernelModule* self);   // Initialize (variable)
    void (*run)(struct KernelModule* self);   // Run Logic (variable)
    void (*exit)(struct KernelModule* self);  // Cleanup
    // Template Method: Fixed Module Lifecycle
    int (*load)(struct KernelModule* self);   // Load Process
    int (*unload)(struct KernelModule* self); // Unload Process
    // Module Information
    const char* name;
    bool is_loaded;
} KernelModule;

// Template Method: Load Process
static int module_load(KernelModule* self) {
    if (self->is_loaded) {
        printf("Module %s is already loaded\n", self->name);
        return -1;
    }
    printf("=== Loading module: %s ===\n", self->name);
    int ret = self->init(self); // Initialize (module-specific)
    if (ret != 0) {
        printf("Module %s init failed (code %d)\n", self->name, ret);
        return ret;
    }
    self->run(self); // Run (module-specific)
    self->is_loaded = true;
    printf("Module %s loaded successfully\n", self->name);
    return 0;
}

// Template Method: Unload Process
static int module_unload(KernelModule* self) {
    if (!self->is_loaded) {
        printf("Module %s is not loaded\n", self->name);
        return -1;
    }
    printf("=== Unloading module: %s ===\n", self->name);
    self->exit(self);
    self->is_loaded = false;
    printf("Module %s unloaded successfully\n", self->name);
    return 0;
}

// Initialize Template Framework
void init_kernel_module(KernelModule* self, const char* name) {
    self->name = name;
    self->is_loaded = false;
    self->load = module_load;
    self->unload = module_unload;
    // Abstract steps initialized to NULL (module must implement)
    self->init = NULL;
    self->run = NULL;
    self->exit = NULL;
}

// 2. Concrete Implementation 1: Network Module
static int net_init(KernelModule* self) {
    printf("[%s] Initializing network module\n", self->name);
    printf("  - Registering network interface\n");
    return 0; // Initialization successful
}

static void net_run(KernelModule* self) {
    printf("[%s] Starting network service (DHCP, DNS)\n", self->name);
}

static void net_exit(KernelModule* self) {
    printf("[%s] Cleaning up network module\n", self->name);
    printf("  - Unregistering network interface\n");
}

// Create Network Module
KernelModule* create_net_module() {
    KernelModule* mod = malloc(sizeof(KernelModule));
    init_kernel_module(mod, "net_module");
    mod->init = net_init;
    mod->run = net_run;
    mod->exit = net_exit;
    return mod;
}

// 具体实现2:存储模块
static int storage_init(KernelModule* self) {
    printf("[%s] Initializing storage module\n", self->name);
    printf("  - Scanning for disks\n");
    printf("  - Mounting filesystems\n");
    return 0;
}

static void storage_run(KernelModule* self) {
    printf("[%s] Starting storage service (I/O scheduling)\n", self->name);
}

static void storage_exit(KernelModule* self) {
    printf("[%s] Cleaning up storage module\n", self->name);
    printf("  - Unmounting filesystems\n");
}

// Create Storage Module
KernelModule* create_storage_module() {
    KernelModule* mod = malloc(sizeof(KernelModule));
    init_kernel_module(mod, "storage_module");
    mod->init = storage_init;
    mod->run = storage_run;
    mod->exit = storage_exit;
    return mod;
}

// Client Usage (Simulate Kernel Module Manager)
int main() {
    // Load Network Module
    printf("--- Network Module ---\n");
    KernelModule* net_mod = create_net_module();
    net_mod->load(net_mod);

    // Load Storage Module
    printf("\n--- Storage Module ---\n");
    KernelModule* storage_mod = create_storage_module();
    storage_mod->load(storage_mod);

    // Unload Modules
    printf("\n--- Unloading Modules ---\n");
    net_mod->unload(net_mod);
    storage_mod->unload(storage_mod);

    free(net_mod);
    free(storage_mod);
    return 0;
}

The execution result of the above code is as follows

--- Network Module ---
=== Loading module: net_module ===
[net_module] Initializing network module
  - Registering network interface
[net_module] Starting network service (DHCP, DNS)
Module net_module loaded successfully

--- Storage Module ---
=== Loading module: storage_module ===
[storage_module] Initializing storage module
  - Scanning for disks
  - Mounting filesystems
[storage_module] Starting storage service (I/O scheduling)
Module storage_module loaded successfully

--- Unloading Modules ---
=== Unloading module: net_module ===
[net_module] Cleaning up network module
  - Unregistering network interface
Module net_module unloaded successfully
=== Unloading module: storage_module ===
[storage_module] Cleaning up storage module
  - Unmounting filesystems
Module storage_module unloaded successfully

The UML diagram is as follows

Application of Template Method Pattern in C Language (Including 5 Examples + Linux Kernel Case Analysis)

That is: Simulating the lifecycle management of Linux kernel modules, all modules follow the fixed process of “Load → Initialize → Run → Unload” (template method <span>load</span>/<span>unload</span>), while the <span>init</span> and <span>run</span> steps vary by module functionality (network / storage), consistent with the mechanism of <span>module_init</span>/<span>module_exit</span> in the kernel.

4. Application of Template Method Pattern in Linux Kernel

  1. 1. Device Driver Model (<span>driver</span> and <span>device</span>): Kernel device drivers follow the fixed process of “Probe → Initialize → Run → Remove”, defined in <span>struct device_driver</span><code><span>. Specific drivers only need to implement </span><code><span>probe</span> and <span>remove</span> steps, reusing the kernel’s driver management framework (template method), such as <span>driver_register</span> and <span>driver_unregister</span>.
  2. 2. File System Mounting Process: All file systems (e.g., ext4, btrfs) have a unified mounting process of “Parse Superblock → Initialize Inode → Mount Root Directory”, defined in the VFS (Virtual File System) template method. Specific file systems connect to the unified mounting framework by implementing the <span>struct file_system_type</span> in the <span>mount</span> function (abstract step).
  3. 3. Kernel Module Lifecycle: Kernel modules must implement <span>init_module</span> (initialization) and <span>cleanup_module</span> (cleanup) interfaces, and the loading (<span>sys_init_module</span>) and unloading (<span>sys_delete_module</span>) processes are uniformly managed by the kernel (template method), ensuring all modules follow the same lifecycle specifications.
  4. 4. Network Protocol Stack Initialization: The initialization process of network protocols (e.g., TCP, UDP) is unified as “Register Protocol → Initialize Socket → Register Hook Functions”, defined in <span>proto_register</span> and other template methods. Specific protocols connect to the kernel’s protocol registration framework by implementing the <span>struct proto</span> in the <span>init</span> function (abstract step).
  5. 5. Work Queue Processing Process: The task processing of work queues follows the fixed process of “Enqueue → Schedule → Execute → Cleanup”, defined in <span>queue_work</span> and other template methods. Users only need to implement the <span>work_struct</span> in the <span>func</span> function (abstract step) to connect tasks to the work queue framework without worrying about scheduling details.

5. Implementation Considerations

  1. 1. Immutability of Template Method: The template method (fixed process) should be designed as immutable core logic to avoid specific implementations from disrupting process consistency. In C language, this can be achieved by implementing the template method as a static function (not allowed to be overridden), exposing only the function pointers of abstract steps.
  2. 2. Completeness of Abstract Steps: Ensure that all abstract steps are correctly initialized in concrete implementations (to avoid null pointers), and add checking logic in the template method to report errors for unimplemented steps (e.g., <span>if (!self->step) { fprintf(stderr, "Step not implemented"); }</span>).
  3. 3. Extensibility of Process: The template method should reserve extension points (e.g., inserting optional hook functions between fixed steps), such as adding a <span>pre_process</span> hook between “Initialize → Process” to support flexible enhancement of the process in concrete implementations without modifying the template method itself.
  4. 4. Safety of Data Sharing: Data shared between the template framework and specific implementations (e.g., struct members) should have clear access rules to avoid specific implementations modifying critical data relied upon by the framework (can be limited by <span>const</span> or encapsulated access interfaces).
  5. 5. Consistency of Error Handling: The template method should unify error handling logic (e.g., return code specifications, log output formats), with specific steps notifying errors through return values, and the template method handling errors uniformly to ensure consistency in error behavior.

6. Additional Notes

  • • Difference between Template Method Pattern and Strategy Pattern: The Template Method Pattern reuses the process framework through inheritance (or struct composition), emphasizing “fixed process, variable steps”; the Strategy Pattern achieves behavior replacement through composing algorithm objects, emphasizing “algorithm independence, dynamic selection”. The former is “framework-driven”, while the latter is “algorithm-driven”.
  • • Variants of Template Method: Can implement “Hook Methods”, which are optional steps defined in the template (default empty implementation), and specific implementations can override as needed, such as adding <span>pre_run</span> and <span>post_run</span> hooks in testing frameworks.
  • • Applicable Scenarios: Scenarios of process standardization (e.g., protocol interaction, device initialization), scenarios where multiple implementations share a framework (e.g., file systems, drivers), and scenarios requiring process consistency (e.g., test cases, transaction processing).

Through the Template Method Pattern, C language programs (especially in the Linux kernel) can efficiently reuse fixed process frameworks while allowing flexible customization of specific steps, reducing code redundancy while ensuring system consistency, and is a core technology for building standardized, extensible systems. Many core components in the kernel adopt this pattern, reflecting the design philosophy of “framework reuse, detail customization”.

Click to read the full article, thank you for sharing, saving, liking, and viewing

Leave a Comment