Skip to content
Home » Software Design » Law of Demeter (Principle of Least Knowledge) in C — With Examples

Law of Demeter (Principle of Least Knowledge) in C — With Examples

Law of Demeter featured image with purple background, Principles badge, LD icon, Principle of Least Knowledge subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 16 of 31View Full Path →

KEY TAKEAWAYS

  • Only talk to your immediate friends — avoid chained struct access like a->b->c->d
  • Each layer should expose functions that operate on its own data
  • Pass only the data a function actually needs, not the entire parent struct
  • The result is code that is more modular, testable, and resilient to change

What Is the Law of Demeter?

The Law of Demeter (LoD), also called the Principle of Least Knowledge, states: a function should only talk to its immediate friends, not to strangers. In practice, this means a function should only access:
  • Its own local variables
  • Its parameters
  • Members of structs it directly owns
It should not reach through one struct to access another struct’s internals. Long chains like a->b->c->value violate the Law of Demeter because the calling code depends on the entire chain of internal structure.

Example 1: Accessing Nested Hardware State

Bad — Reaching Through Struct Chains

typedef struct {
    int raw_value;
    float calibration_offset;
} ADC;

typedef struct {
    ADC *adc;
    int channel;
} Sensor;

typedef struct {
    Sensor *sensors[8];
    int sensor_count;
} SensorHub;

// Violates LoD — reaches 3 levels deep
float get_calibrated_reading(SensorHub *hub, int idx) {
    int raw = hub->sensors[idx]->adc->raw_value;
    float offset = hub->sensors[idx]->adc->calibration_offset;
    return (float)raw * 0.01f + offset;
}

// What breaks:
// - If ADC struct changes, this function changes
// - If Sensor changes how it stores its ADC, this function changes
// - Tightly coupled to the ENTIRE internal structure

Good — Ask, Don’t Dig

// Each level exposes only what it should

float adc_read_calibrated(const ADC *adc) {
    return (float)adc->raw_value * 0.01f + adc->calibration_offset;
}

float sensor_read(const Sensor *s) {
    return adc_read_calibrated(s->adc);
}

float hub_read_sensor(const SensorHub *hub, int idx) {
    return sensor_read(hub->sensors[idx]);
}

// Caller only talks to its immediate friend
float temp = hub_read_sensor(hub, 3);
Each function only accesses one level. If the ADC struct changes, only adc_read_calibrated needs updating. The hub and the application code are completely insulated.

Example 2: Configuration Access

Bad — Deeply Coupled Config Access

typedef struct {
    int baud_rate;
    int parity;
} UartConfig;

typedef struct {
    UartConfig uart;
    int retry_count;
} CommConfig;

typedef struct {
    CommConfig comm;
    int log_level;
} SystemConfig;

// This function "knows" the entire config structure
void init_uart(SystemConfig *cfg) {
    uart_set_baud(cfg->comm.uart.baud_rate);   // 3 levels deep
    uart_set_parity(cfg->comm.uart.parity);     // 3 levels deep
}

Good — Pass Only What Is Needed

// Pass the specific config the function needs
void init_uart(const UartConfig *cfg) {
    uart_set_baud(cfg->baud_rate);
    uart_set_parity(cfg->parity);
}

// Caller extracts the right level
void system_init(SystemConfig *cfg) {
    init_uart(&cfg->comm.uart);  // Only system_init knows the structure
}
The init_uart function only knows about UartConfig. It does not know it came from a SystemConfig. This makes it reusable with any UartConfig source and testable in isolation.

Why the Law of Demeter Matters

  • Reduces coupling — changing internal struct layout only affects one layer
  • Improves testability — functions that accept minimal parameters are easier to test
  • Makes refactoring safe — you can restructure internals without cascading changes
  • Clarifies responsibility — each module is responsible for its own data access

When to Relax the Rule

  • Data transfer structs — simple structs used only to carry data (like a Point with x, y) are fine to access directly
  • Performance-critical code — adding wrapper functions in tight loops may not be worth the overhead in embedded systems
  • Fluent-style APIs — chained method calls are common in some designs and acceptable when the chain is the API contract

Key Takeaways

  • Only talk to your immediate friends — avoid chained struct access like a->b->c->d
  • Each layer should expose functions that operate on its own data
  • Pass only the data a function actually needs, not the entire parent struct
  • The result is code that is more modular, testable, and resilient to change

Leave a Reply

Your email address will not be published. Required fields are marked *