Skip to content

Demeter's Law

Core Idea

Demeter's Law guides object collaboration boundaries. A unit should talk only to its direct collaborators. This rule is known as the least knowledge principle. The rule reduces coupling and limits change ripple.

Conceptual Overview

A method should call members on:

  • Itself
  • Its direct fields
  • Objects received as parameters
  • Objects created inside the method

The rule rejects deep chains such as obj.a().b().c().run().

Deep call chains expose internal structure across boundaries. A small schema or model change then breaks external callers. Tests grow fragile and require more mocking detail. Maintenance cost rises with each leaked dependency path.

Demeter's Law supports encapsulation directly. Encapsulation hides internal navigation details. Callers ask for behavior through one clear method.

All examples below use the shared handbook application context: Hypothetical Scenario.

Direct collaborators in Demeter's Law

Direct collaborator map. Method calls stay within one boundary step.

Computing History

Demeter's Law came from research at Northeastern University in the late 1980s. Karl Lieberherr and his colleagues studied object interaction patterns. They observed that deep object navigation increased change cost. The rule was published in 1988 and spread across OOP practice.

Sources: Lieberherr, Holland, and Riel (1988)

Quote

"The structure of one object should not be known by another object."

Source: Lieberherr, Holland, and Riel, 1988

Generic Example: Entry Task

@worker.task(name="refresh_marketplace_matches")
def refresh_marketplace_matches(task_data: dict) -> None:
    job = RefreshMarketplaceMatchesJob(
        task_data=task_data,
        market_db_config=get_market_db_config(),
        price_clock=get_price_clock_service(),
    )
    asyncio.set_event_loop(uvloop.new_event_loop())
    loop = asyncio.get_event_loop()
    loop.run_until_complete(job.run())

This entry point builds one object and passes dependencies. It does not navigate database internals directly. It delegates behavior to the job object.

Generic Example: Operation Class

class GetCarRecommendationSummary:
    def __init__(self, profile_id, filters, market_db_config, price_clock):
        self.profile_id = profile_id
        self.filters = filters
        self.market_db_config = market_db_config
        self.price_clock = price_clock

    async def response(self):
        service = CarRecommendationSummaryService(
            profile_id=self.profile_id,
            filters=self.filters,
            market_db_config=self.market_db_config,
            price_clock=self.price_clock,
        )
        return await service.run()

The operation class calls one service boundary. It does not chain through nested storage objects.

Anti Pattern and Better Version

Non compliant:

price = car_match_service.db.client.listings.find_one({"vin": vin})["market_value"]

Issue:

  • Caller depends on deep storage structure.
  • Storage refactor can break many callers.

Compliant:

price = await car_match_service.get_market_value_by_vin(vin)

Why this is better:

  • Caller uses one public behavior method.
  • Storage path stays private to service internals.
  • Change impact stays local to one boundary.

Deep chain versus boundary call

Chain versus boundary comparison. One boundary call keeps internal structure hidden.

Change ripple comparison

Ripple impact view. Demeter violations spread change cost across callers.

This principle links to Abstraction and Boundaries. Direct collaborator rules keep boundaries narrow.

This principle links to Modularity and Composition. Modules compose cleanly when call paths stay shallow.

This principle links to Simplicity First. Short call paths reduce accidental complexity.

This principle links to Correctness and Testing. Boundary-safe calls reduce brittle test setup.

This principle aligns with Class Design. Encapsulation and visibility controls support direct collaborator rules.

This principle aligns with The Multiple Assert Problem. High chain coupling tends to produce brittle and broad tests.

Practice Checklist

  • Keep object calls to one boundary step.
  • Reject deep call chains in reviews.
  • Expose behavior methods instead of data paths.
  • Hide nested object graphs behind service methods.
  • Track Demeter violations as refactor tasks.

Written by: Pedro Guzmán

See References for complete APA-style bibliographic entries used on this page.