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 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:
Issue:
- Caller depends on deep storage structure.
- Storage refactor can break many callers.
Compliant:
Why this is better:
- Caller uses one public behavior method.
- Storage path stays private to service internals.
- Change impact stays local to one boundary.
Chain versus boundary comparison. One boundary call keeps internal structure hidden.
Ripple impact view. Demeter violations spread change cost across callers.
Foundational Principle Links
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.
Related Design Pages
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.