Once And Only Once
Every distinct behavior must be implemented in exactly one place.
OAOO Principle: A Complete Guide with Examples in 11 Programming Languages
OAOO — Once And Only Once — is one of the original principles of Extreme Programming (XP), articulated by Kent Beck and Ward Cunningham. It states that every distinct behavior or piece of functionality in a system should be implemented in exactly one place. When the system needs that behavior, it calls the one place where it is defined. It never re-implements it. It never duplicates it. The behavior exists exactly once — and only once.
OAOO is a sharper, more behavior-focused cousin of DRY. Where DRY covers all forms of knowledge — constants, schemas, documentation, algorithms, configuration — OAOO zooms in specifically on the behavioral implementations in code: functions, methods, and modules that do something. If two functions in your codebase both implement the same behavior — even if written differently — OAOO is violated.
In this comprehensive guide, we'll explore the OAOO Principle with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — covering how to recognize OAOO violations, why they compound over time, how to refactor toward OAOO compliance, and how OAOO differs from DRY in its practical application.
Table of Contents
- What is the OAOO Principle?
- Why Apply OAOO?
- OAOO vs. DRY and Related Principles
- OAOO Principle Explained
- Concept Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the OAOO Principle?
OAOO addresses a deceptively common problem in growing systems: the same behavior is implemented more than once. Not because code was copy-pasted (though it often is) — but because a developer needed a behavior and wrote it again instead of finding the existing implementation. Or because the system grew and two independent features converged on implementing the same underlying operation.
The naive approach is to implement what you need, where you need it. If you need to compute a discount, write a discount function in the checkout module. Later, the admin panel needs the same discount — write another one there. The reporting module needs it — write a third. Now the business changes the discount rule. Three implementations exist. One gets updated. Two get missed. The system produces three different discount calculations depending on the code path.
OAOO resolves this by demanding that the discount behavior — like every behavior — live in exactly one function, in exactly one module. The checkout calls it. The admin panel calls it. The reporting module calls it. When the rule changes, the change is made once. All callers are correct immediately.
Think of it like a recipe. If the recipe for your company's signature sauce exists in three separate kitchen manuals — each slightly different — the restaurant serves three different sauces depending on which chef read which manual. If the recipe exists in one canonical place that all chefs reference, every customer gets the same sauce, and when the head chef refines the recipe, every kitchen gets the update simultaneously.
Why Apply OAOO?
The OAOO Principle delivers concrete benefits that compound over the lifetime of a system:
- Behavioral Consistency: Every caller of a behavior gets exactly the same result — no silent divergence between implementations
- Single Correction Point: When a behavior has a bug or needs updating, there is exactly one function to fix — no hunting for copies
- Comprehensive Testing: A behavior tested in one implementation covers all its callers — duplicated implementations require proportionally more tests to achieve the same coverage
- Fearless Refactoring: Improving a behavior — optimizing, hardening, extending — benefits all callers automatically when the behavior is implemented once
- Living Documentation: A well-named function that implements a behavior once is clearer than the same logic scattered across the codebase in unnamed inline blocks
- Reduced Cognitive Load: Developers reading a codebase with OAOO compliance can understand behaviors by finding their single definition — no need to cross-reference multiple implementations
OAOO vs. DRY and Related Principles
| Principle | Scope | Focus | Primary Tool |
|---|---|---|---|
| OAOO | Behavioral implementations | Functions and methods that do something | Extract function, delegate |
| DRY | All knowledge | Logic, data, config, schema, docs | Functions, constants, types, config |
| SSOT | Data and state | Where data lives and is mutated | Database normalization, state stores |
| SRP | Module responsibility | How much a module knows/does | Decompose classes, separate concerns |
| Abstraction | Design quality | Hiding implementation details | Interfaces, modules, encapsulation |
Key Distinctions:
- OAOO vs. DRY: DRY is the broader principle — it governs all forms of knowledge, including constants, type definitions, configuration, and documentation. OAOO is specifically about behavioral implementations: functions and methods that implement a specific operation. Every OAOO violation is a DRY violation. Not every DRY violation is an OAOO violation (a duplicated constant is DRY but not OAOO). When you need to DRY up behavior, OAOO is the specific rule you're applying.
- OAOO vs. SRP: SRP governs how much a module (class or function) should do — it should have one reason to change. OAOO governs how many places a behavior should be implemented — exactly one. They are complementary: SRP says keep each module focused; OAOO says don't split a behavior across modules.
- OAOO vs. Polymorphism: Polymorphism (interfaces, virtual dispatch) is often the mechanism used to achieve OAOO across related types. Instead of duplicating behavior in each subtype, the behavior is defined once in the interface/base and varied through polymorphic dispatch. Polymorphism is a tool; OAOO is the goal it serves.
OAOO Principle Explained
OAOO manifests at multiple levels:
Levels of Behavioral Duplication:
-
Method-level Duplication: Two methods in the same class or module that implement the same operation with different names or slight variations. The classic symptom:
calculatePrice()andcomputeTotal()that both apply the same discount formula. -
Cross-class Duplication: The same behavior implemented independently in two or more classes. A
UserServiceand anAdminServicethat both independently validate email addresses rather than calling a sharedEmailValidator. -
Layer Duplication: The same behavior implemented in multiple architectural layers — a business rule in both the service layer and the API controller, because the developer wasn't sure which layer owned it.
-
Algorithm Re-implementation: A utility function (string slugification, date parsing, number rounding) reimplemented from scratch in multiple places instead of being extracted to a shared utility.
-
Template Duplication: The same control-flow template (try-catch-finally, retry loop, loading state management) duplicated across classes that all need the same structure.
How to Achieve OAOO:
- Extract function: When behavior appears more than once, extract it to a named function and have all occurrences call it
- Use base classes or mixins: When a behavior is shared across multiple classes in an inheritance hierarchy, implement it once in the base
- Create service or utility classes: Cross-cutting behaviors (formatting, validation, parsing) belong in dedicated service or utility classes that all callers reference
- Apply the Template Method pattern: When multiple classes follow the same behavioral skeleton but vary in steps, define the skeleton once and vary only the steps
- Use composition over inheritance: Instead of each class reimplementing shared behavior, inject a shared component that implements the behavior once
Where OAOO Tension Arises:
OAOO can be over-applied. When the "same" behavior in two contexts is really two different behaviors that happen to produce the same result today — unifying them creates accidental coupling. If UserEmailValidator and AdminEmailValidator both check for @ today, but the admin validator will gain additional domain-specific rules tomorrow, unifying them prematurely creates a single function that becomes increasingly complex to handle both cases. The OAOO test: is this one behavior with one definition, or two behaviors that happen to look the same today?
Concept Diagram
Here's a diagram showing OAOO-compliant vs. OAOO-violating behavior organization:
Beginner-Friendly Example
The clearest OAOO example: a discount calculation that starts implemented in one place, then gets re-implemented independently in other places as the system grows. We'll see the violation, understand why it's harmful, and then apply the OAOO fix.
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
category: str
# ══════════════════════════════════════════════════════════════════
# ❌ OAOO VIOLATION: Discount calculation implemented independently in 3 places
# ══════════════════════════════════════════════════════════════════
class CheckoutService:
def calculate_total(self, product: Product, quantity: int, discount_pct: float) -> float:
# Behavior #1: Discount applied inline
subtotal = product.price * quantity
return subtotal * (1 - discount_pct / 100)
class InvoiceService:
def line_item_total(self, product: Product, quantity: int, discount_pct: float) -> float:
# Behavior #2: Same calculation — but are these REALLY the same behavior?
# Today yes. Tomorrow: invoice might use floor rounding; checkout might cap discounts.
unit_price = product.price * (1 - discount_pct / 100)
return unit_price * quantity # Note: different arithmetic order — same result, but fragile
class ReportService:
def order_value(self, product: Product, quantity: int, discount_pct: float) -> float:
# Behavior #3: Third implementation — change "what is discount?" and fix 3 classes
return (product.price * quantity) - (product.price * quantity * discount_pct / 100)
# ══════════════════════════════════════════════════════════════════
# ✅ OAOO COMPLIANT: Discount behavior defined once — all callers delegate
# ══════════════════════════════════════════════════════════════════
class PricingEngine:
"""Single definition of all pricing behavior."""
@staticmethod
def apply_discount(unit_price: float, quantity: int, discount_pct: float) -> float:
"""Calculate discounted line total. Defined once — called everywhere."""
if not 0 <= discount_pct <= 100:
raise ValueError(f"Discount percentage must be between 0 and 100, got {discount_pct}")
subtotal = unit_price * quantity
return round(subtotal * (1 - discount_pct / 100), 2)
@staticmethod
def apply_category_discount(product: Product, quantity: int) -> float:
"""Category-based discount logic — defined once for all category callers."""
category_discounts = {"electronics": 5.0, "apparel": 10.0, "books": 15.0}
discount = category_discounts.get(product.category, 0.0)
return PricingEngine.apply_discount(product.price, quantity, discount)
class CheckoutService:
def calculate_total(self, product: Product, quantity: int, discount_pct: float) -> float:
return PricingEngine.apply_discount(product.price, quantity, discount_pct)
class InvoiceService:
def line_item_total(self, product: Product, quantity: int, discount_pct: float) -> float:
return PricingEngine.apply_discount(product.price, quantity, discount_pct)
class ReportService:
def order_value(self, product: Product, quantity: int, discount_pct: float) -> float:
return PricingEngine.apply_discount(product.price, quantity, discount_pct)
# Discount rule changes? Fix PricingEngine.apply_discount once. All three stay correct.
# ── Demo ──────────────────────────────────────────────────────────
if __name__ == "__main__":
laptop = Product("Laptop", 1000.0, "electronics")
print(f"Checkout total: ${CheckoutService().calculate_total(laptop, 2, 10):.2f}")
print(f"Invoice total: ${InvoiceService().line_item_total(laptop, 2, 10):.2f}")
print(f"Report value: ${ReportService().order_value(laptop, 2, 10):.2f}")
print(f"Category price: ${PricingEngine.apply_category_discount(laptop, 2):.2f}")
Production-Ready Example
Now let's build a data export pipeline that generates reports in CSV, JSON, and XML formats. Without OAOO, the filtering, sorting, and field-selection logic gets re-implemented inside each exporter. With OAOO, the data preparation behavior is defined once, and each exporter handles only its format-specific serialization.
🐍 Python (Production-Ready)
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Dict, Any, Protocol
from abc import abstractmethod
import json
import csv
import io
# ── Domain ────────────────────────────────────────────────────────
@dataclass
class SaleRecord:
id: int
product: str
category: str
amount: float
region: str
month: int
# ══════════════════════════════════════════════════════════════════
# ❌ OAOO VIOLATION: Each exporter re-implements filtering and sorting
# ══════════════════════════════════════════════════════════════════
class CsvExporterWet:
def export(self, records: List[SaleRecord], min_amount: float = 0) -> str:
# Filter inline — behavior #1
filtered = [r for r in records if r.amount >= min_amount]
# Sort inline — behavior #2
sorted_records = sorted(filtered, key=lambda r: r.amount, reverse=True)
out = io.StringIO()
writer = csv.writer(out)
writer.writerow(["id", "product", "amount", "region"])
for r in sorted_records:
writer.writerow([r.id, r.product, r.amount, r.region])
return out.getvalue()
class JsonExporterWet:
def export(self, records: List[SaleRecord], min_amount: float = 0) -> str:
# Filter re-implemented — behavior #3
filtered = [r for r in records if r.amount >= min_amount]
# Sort re-implemented — behavior #4
sorted_records = sorted(filtered, key=lambda r: r.amount, reverse=True)
return json.dumps([{"id": r.id, "product": r.product, "amount": r.amount} for r in sorted_records])
# ══════════════════════════════════════════════════════════════════
# ✅ OAOO COMPLIANT: Data preparation once — format-specific serialization in each exporter
# ══════════════════════════════════════════════════════════════════
@dataclass
class ExportConfig:
min_amount: float = 0.0
categories: List[str] = field(default_factory=list) # Empty = all categories
sort_by: str = "amount"
descending: bool = True
fields: List[str] = field(default_factory=lambda: ["id", "product", "category", "amount", "region"])
class DataPreparer:
"""Single implementation of all data preparation behaviors."""
@staticmethod
def prepare(records: List[SaleRecord], config: ExportConfig) -> List[Dict[str, Any]]:
# Filter behavior — defined once
filtered = [
r for r in records
if r.amount >= config.min_amount
and (not config.categories or r.category in config.categories)
]
# Sort behavior — defined once
sorted_records = sorted(
filtered,
key=lambda r: getattr(r, config.sort_by, r.amount),
reverse=config.descending
)
# Field selection behavior — defined once
return [
{f: getattr(rec, f) for f in config.fields if hasattr(rec, f)}
for rec in sorted_records
]
class Exporter(Protocol):
"""Contract: exporters receive prepared data and handle only serialization."""
@abstractmethod
def serialize(self, rows: List[Dict[str, Any]]) -> str: ...
@abstractmethod
def content_type(self) -> str: ...
class CsvExporter:
def serialize(self, rows: List[Dict[str, Any]]) -> str:
if not rows: return ""
out = io.StringIO()
writer = csv.DictWriter(out, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
return out.getvalue()
def content_type(self) -> str:
return "text/csv"
class JsonExporter:
def serialize(self, rows: List[Dict[str, Any]]) -> str:
return json.dumps(rows, indent=2)
def content_type(self) -> str:
return "application/json"
class XmlExporter:
def serialize(self, rows: List[Dict[str, Any]]) -> str:
lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<records>"]
for row in rows:
lines.append(" <record>")
for k, v in row.items():
lines.append(f" <{k}>{v}</{k}>")
lines.append(" </record>")
lines.append("</records>")
return "\n".join(lines)
def content_type(self) -> str:
return "application/xml"
class ReportGenerator:
"""Orchestrates: prepare once, serialize in any format."""
def __init__(self, preparer: DataPreparer) -> None:
self._preparer = preparer
def generate(self, records: List[SaleRecord], config: ExportConfig, exporter: Exporter) -> str:
rows = self._preparer.prepare(records, config) # Data prep runs ONCE
return exporter.serialize(rows) # Serialization is format-specific
# ── Client ────────────────────────────────────────────────────────
if __name__ == "__main__":
records = [
SaleRecord(1, "Laptop", "electronics", 1200.0, "West", 1),
SaleRecord(2, "T-Shirt", "apparel", 40.0, "East", 1),
SaleRecord(3, "Phone", "electronics", 800.0, "West", 2),
SaleRecord(4, "Headset", "electronics", 150.0, "North", 2),
SaleRecord(5, "Notebook", "office", 25.0, "South", 1),
]
config = ExportConfig(
min_amount=100.0,
categories=["electronics"],
sort_by="amount",
descending=True,
fields=["id", "product", "amount", "region"]
)
generator = ReportGenerator(DataPreparer())
print("=== CSV ===")
print(generator.generate(records, config, CsvExporter()))
print("\n=== JSON ===")
print(generator.generate(records, config, JsonExporter()))
print("\n=== XML ===")
print(generator.generate(records, config, XmlExporter()))
# Add XML exporter? Just implement serialize() — no data prep logic needed.
# Filter rule changes? Fix DataPreparer.prepare — all formats get the same fix.
Real-World Use Cases
1. Authentication Middleware
In web applications, the authentication check — verifying the token, loading the user session, returning an unauthorized response — should be implemented once in middleware. Every endpoint that requires authentication calls the middleware; none of them re-implement the check. Adding a new endpoint does not require copying authentication logic.
2. Data Serialization
The serialization of domain objects to wire format (JSON, XML, Protobuf) should be defined once per type. When a Product is serialized, there is one serializeProduct() function that every API endpoint and export function uses. Adding a new field to Product requires one update to the serialization function — not hunting every endpoint.
3. Business Rule Engines
Pricing rules, eligibility checks, and compliance validations that apply across multiple features should be defined once in a rules engine or domain service. The checkout page, the admin dashboard, the import API, and the reporting module all call the same rule — they never re-implement it.
4. Error Handling Pipelines
The logic for catching an exception, logging it, translating it to a user-friendly message, and producing an error response should be implemented once in an error handler. Every endpoint delegates to it. Adding a new exception type is updating the handler, not every endpoint.
5. Repository Patterns
Data access queries that are logically the same operation (fetch active users, get recent orders, find products in category) should be implemented once in repository methods. Controllers and service methods call the repository — they never inline queries.
6. Event Processing
An event-driven system where the same event (user signed up, order placed) triggers multiple handlers. The event definition is once — its structure, fields, and meaning are canonical. Handlers subscribe to the event and process it; they don't re-implement what the event is.
7. Rendering Pipelines
UI components that render the same type of data (a product card, a user avatar, a notification item) should be implemented once. Other components that need to show a product call the product card component — they don't re-render a product themselves.
Language-Specific Mistakes and Anti-Patterns
# OAOO VIOLATION: Same validation implemented in each subclass
class AdminUser:
def validate_email(self, email):
return "@" in email # Independent implementation
class RegularUser:
def validate_email(self, email):
return "@" in email # Same behavior — re-implemented
# OAOO COMPLIANT: Behavior defined once in base class
class BaseUser:
def validate_email(self, email):
return "@" in email # Defined once
class AdminUser(BaseUser): pass # Inherits behavior
class RegularUser(BaseUser): pass # Inherits same behavior
Frequently Asked Questions
Q1: What's the exact difference between OAOO and DRY?
DRY says: every piece of knowledge must have a single, unambiguous representation. Knowledge includes logic, constants, schemas, configuration, and documentation. OAOO says: every distinct behavior (a function that does something) must be implemented exactly once. OAOO is a specific application of DRY to behavioral code. All OAOO violations are DRY violations. A duplicated constant is a DRY violation but not an OAOO violation.
In practice: when you're consolidating duplicated business logic or algorithms, you're applying OAOO. When you're centralizing a magic number or a shared type, you're applying DRY (but not specifically OAOO).
Q2: How does OAOO interact with polymorphism?
Polymorphism is one of the main mechanisms for achieving OAOO across related types. Instead of each class in a hierarchy re-implementing behavior, the behavior is defined once in the interface or base class and varied through polymorphic dispatch. The single canonical definition lives in the interface; the variations live in the implementations. This is OAOO-compliant: the behavior (the interface method) is defined once; its implementation variations are separate strategies.
Q3: Can OAOO be violated by code that looks different?
Yes. OAOO is about semantic equivalence, not syntactic similarity. Two functions that compute the same result through different arithmetic expressions both implement the same behavior — that's an OAOO violation even though the code looks different. The test: if the underlying rule changed, would both functions need to change? If yes — OAOO violation.
Q4: How do I apply OAOO to UI templates?
Extract repeated UI markup into components. A user avatar rendered in three places — header, sidebar, comment list — should be a single <UserAvatar> component. Shared layout patterns (card containers, section headers, empty state illustrations) should be components referenced everywhere. This is OAOO applied to the view layer.
Q5: How does OAOO affect performance?
OAOO occasionally creates a tension with performance. A frequently-called shared function may be a performance bottleneck; an inlined version in a hot path might be faster. In performance-critical code, you may deliberately inline a behavior (accepting the OAOO violation) after profiling confirms the shared function is the bottleneck. This should be a deliberate, documented exception — not the default.
Q6: What is the "Rule of Two" vs "Rule of Three" for OAOO?
The Rule of Three advises waiting for three instances before extracting. For OAOO specifically, if you are confident the two instances represent the same behavior (same rule, same definition, same change triggers), don't wait for three — extract on two. The Rule of Three is a guard against premature abstraction; if you're certain the behavior is the same, there's no reason to wait.
Q7: Does OAOO apply to test code?
Yes. Test behavior — creating fixtures, asserting complex conditions, setting up mocks — should be defined once and reused. A test utility function that creates a valid User object with defaults is OAOO for test setup. Custom matchers or assertion helpers are OAOO for assertions. However, OAOO applied too aggressively to test content can make tests harder to understand. Test structure (setup, teardown, helpers) should be OAOO; test assertions may be deliberately explicit even at the cost of some repetition.
Q8: How do I enforce OAOO in a large codebase?
The primary tools are code review (reviewers who ask "does this behavior already exist?"), architecture documentation (a behaviors.md or architecture guide listing canonical implementations), and naming conventions (everyone knows that email validation lives in Validator.validateEmail()). Automated duplication detectors help with syntactic copies; semantic enforcement requires human review and team culture.
Q9: Can a behavior be "too centralized"?
Yes. If extracting a behavior creates a God Function that handles too many variations via parameters and conditionals, you've over-applied OAOO. The abstraction's complexity now exceeds the duplication it replaced. The sign: a shared function that needs 6 boolean parameters to handle all its callers. At that point, the "shared" behavior was actually multiple distinct behaviors that only superficially resembled each other. Split the abstraction back into the distinct behaviors.
Q10: What tools help identify OAOO violations?
Static analysis tools: SonarQube and its "duplicate code" rules, PMD's Copy-Paste Detector, and language-specific tools like pylint (Python), clang-tidy (C++), and ReSharper (C#) can detect syntactic duplication. For semantic duplication (same behavior, different code), the primary detection mechanism is code review and team familiarity with the codebase.
Key Takeaways
🎯 Core Concept: OAOO (Once And Only Once) states that every distinct behavior in a system should be implemented in exactly one place. All callers of that behavior delegate to the single implementation — they never re-implement it. When the behavior must change, exactly one implementation is updated, and all callers are correct automatically. OAOO is a sharper, more behavior-focused application of the broader DRY principle.
🔑 Key Benefits:
- Behavioral consistency: Every caller gets exactly the same result — no silent divergence between implementations
- Single correction point: One function to fix when a behavior has a bug — all callers heal immediately
- Comprehensive coverage: A behavior tested once covers all its callers — no proportional test overhead
- Fearless improvement: Optimizing or hardening a behavior benefits all callers automatically
- Clear ownership: A well-named single implementation communicates where a behavior lives and who owns it
🌐 Language-Specific Highlights:
- Python: Use static methods in a dedicated service class for shared behaviors;
Protocolfor structural typing of shared interfaces;@functools.cacheto make shared functions efficient - TypeScript: Generic functions (
indexById<T extends { id: number }>) for behaviors that work across types; module-level functions for shared behaviors; avoid re-implementing behavior in class hierarchies - Java:
finalutility classes for shared behaviors;defaultinterface methods for shared behavior across implementors;recorddelegating to a shared serializer - JavaScript: Factory functions for parameterized shared behaviors; module exports for canonical behavior functions; avoid re-implementing behavior in event handlers
- C#:
staticauthorization/guard classes; extension methods for reusable behaviors on existing types;abstractbase methods for behaviors shared across a class hierarchy - PHP: Traits for shared behaviors across unrelated classes; Blade/Twig components for shared UI behaviors; base controller for shared request-handling behaviors
- Go: Package-level functions for shared behaviors; function types for parameterized behaviors; interfaces for behavioral contracts that multiple types implement
- Rust: Traits for behavioral contracts;
impl Traitblocks for shared default behaviors;From/Intodelegating to a canonical parsing function - Dart: Mixins for shared stateful behaviors in widgets; abstract classes with default method implementations; extension methods for shared behaviors on existing types
- Swift: Protocol extensions for shared default behaviors; protocol-oriented programming as the primary mechanism for OAOO across types;
final classsingleton services for shared stateful behaviors - Kotlin: Extension functions on base types;
abstractclass with shared logic that subclasses call viasuper;companion objectfor shared factory and utility behaviors
📋 When to Apply OAOO:
- The same operation appears more than once in the codebase
- Multiple classes independently implement the same behavior
- A bug fix in one implementation must also be applied to other implementations
- Adding a new caller requires copying an existing implementation
- A behavior change requires updating more than one function
⚠️ When Not to Over-Apply OAOO:
- Behaviors that look the same today but will diverge for different reasons tomorrow
- Cross-service behavior that should remain independently evolvable
- Performance-critical hot paths where shared function call overhead is measurable
- Behaviors that need so many parameters to handle all callers that the abstraction is harder than the duplication
🛠️ Best Practices Across Languages:
- Name behaviors explicitly: A function called
calculateShippingCost()is self-documenting; unnamed inline logic in five functions is not - Separate behavior from invocation: The function that defines a behavior should not also call it — keep definition and usage distinct
- Test behaviors directly: The single implementation of a behavior should have its own tests — every caller benefits through the shared tests
- Use the Template Method when structure is shared: When multiple functions follow the same skeleton, Template Method implements the skeleton once and varies only the steps
- Extract before the third copy: For clearly identical behaviors, extract on the second occurrence — don't wait for three copies to reveal the pattern
- Document canonical locations: An architecture guide listing where canonical behaviors live prevents future developers from re-implementing
💡 Common Pitfalls:
- Layer-split behaviors: A business rule implemented in both the service layer and the API controller — two layers, two implementations, same behavior
- Subclass re-implementation: Subclasses re-implementing behaviors that should be inherited from the base class
- Parallel inheritance hierarchies: Two class hierarchies that implement the same behavior — a sign that composition would serve OAOO better
- Re-implementation vs. overriding: Overriding a behavior to change it is correct polymorphism; overriding to duplicate the parent behavior is an OAOO violation
- Generic aversion: Avoiding generics or templates leads to re-implementing the same behavior for each specific type — generics are the primary language tool for OAOO across types
🎁 Advanced Techniques:
- Template Method Pattern: Define the behavior skeleton once; let subclasses vary only the steps — OAOO at the structural level
- Visitor Pattern: Define a traversal behavior once; vary the processing through visitors — OAOO for tree/graph traversal with varying operations
- Decorator for OAOO cross-cutting: Cross-cutting behaviors (logging, caching, retry) defined once in decorators, applied via composition rather than re-implemented in each target
- Protocol Extensions (Swift) / Default Interface Methods (Java/Kotlin): Implement a behavior once in the protocol/interface extension — all conforming types get it for free without re-implementation
- Macro / Code Generation: When OAOO cannot be achieved through runtime abstraction (boilerplate that must appear at compile time), code generation implements the behavior logic once in the generator — the generated code is a single source, not a hand-maintained duplicate
OAOO is a discipline: every time you find yourself writing something you've already written, stop. Find it, name it, extract it, and delegate to it. The discipline compounds — a codebase where every developer applies OAOO consistently becomes progressively more coherent, more testable, and more maintainable as it grows.
Explore the full DRY family of principles:
- DRY Principle (Don't Repeat Yourself) — The Core Principle
- WET Principle (Write Everything Twice) — Understanding DRY Violations
- SSOT Principle (Single Source of Truth) Each guide includes examples in all 11 programming languages with language-specific best practices.