Inheritance vs Composition: What the Independent Variation Principle Actually Tells Us
“Favor composition over inheritance” is one of the most cited design guidelines in software engineering. It is tempting to read this as a rule IVP prescribes. It is not.
The Independent Variation Principle (IVP) is precise about what it governs and what it leaves open. This post uses a payment-processing example to show what IVP actually tells us about inheritance and composition — and, just as importantly, what it does not.
1 What IVP governs — and what it does not
IVP’s normative content is a module partition, not a mechanism choice [1]. Given a system’s elements and its set of change drivers , IVP assigns each element a set of change drivers — the change drivers whose state changes create a modification requirement for — and prescribes, in its structural formulation [1]:
- Admissibility — every element has at least one change driver: .
- Element Form — every element is either pure () or irreducibly composite: no proper subset of its driver set can achieve its purpose, and no decomposition into sub-elements with strictly smaller driver sets jointly preserves that purpose.
- Separation — elements assigned different sets of change drivers reside in different modules: .
- Unification — elements assigned the same set of change drivers reside in the same module: .
Equivalently, the modules of an IVP-compliant system are the -equivalence classes of [1]. The first two clauses are the ones the composition-vs-inheritance discussion must not skip. Admissibility says every element answers to some change driver. Element Form ties the assignment to purpose and to the domain knowledge the element embodies: is not an arbitrary label — embodies the knowledge governed by its drivers, so the assignment is grounded in what the element exists to realize [2]. Separation and Unification then say how those grounded assignments group elements into modules.
Inheritance and composition are both such mechanisms. The theory treats inheritance — class B extends A — as a structural dependency: a relation observable directly in the artifact, like a function call or an import. IVP does not have a “composition directive” and an “inheritance directive.” What it does have is a coupling criterion, and that is where the real guidance lies.
2 What IVP mandates: its own minimal coupling
IVP’s coupling criterion follows from its rule [1]. The partition induced by -equivalence is fixed by the change drivers; the coupling that survives a compliant partition is IVP’s minimal coupling — the necessary coupling: essential same- coupling inside a module, plus the essential cross- coupling between modules that is the irreducible cost of communication between -classes.
One thing IVP’s minimal coupling forbids:
- Accidental coupling — coupling not required by any domain artifact. Two elements are coupled when one depends on something the other defines or maintains; that dependency is essential when a domain artifact (a statute, contract, standard, specification) requires it, and accidental when no domain artifact mandates it — a framework convention, a language limitation, a deployment detail.
The accidental coupling IVP is most concerned with is cross- accidental coupling — accidental coupling that connects elements assigned different sets of change drivers.
IVP also leaves no room for accidental coupling between elements that should be apart. If two elements with different change-driver sets sit in the same module and are accidentally coupled, the module is impure: the partition has co-located elements that Separation would keep in different modules, and the system does not faithfully embody the domain’s change structure [1]. IVP’s Separation directive eliminates this case by construction — cross- elements are never co-located in a compliant partition. The only accidental coupling that can remain inside a module is the same- kind — a concrete reference where an abstraction would do — and that is a code-quality defect, not a failure to embody the domain.
Because the coupling verdict is relative to the chosen rule, it is not a property of the code in the abstract — it is a property of the code under a given partition. IVP’s verdict is IVP’s because IVP picks -equivalence as its modularization rule. A different rule would classify the same dependencies differently.
3 The payment example: what IVP diagnoses
Consider a payment-processing system designed with an inheritance hierarchy:
abstract class Payment {
// Infrastructure methods
protected void logTransaction(String details) { /* ... */ }
protected void detectFraud() { /* ... */ }
protected void sendNotification() { /* ... */ }
// Template method pattern
public final void process() {
validate();
detectFraud();
executePayment();
logTransaction("Payment processed");
sendNotification();
}
protected abstract void validate();
protected abstract void executePayment();
}
class CreditCardPayment extends Payment {
protected void validate() { /* credit card validation */ }
protected void executePayment() { /* credit card processing */ }
}
class PayPalPayment extends Payment {
protected void validate() { /* PayPal validation */ }
protected void executePayment() { /* PayPal processing */ }
}IVP’s diagnosis of this structure is specific. Two different change drivers are at work:
- Payment-method variety — the set of accepted payment methods (credit card, PayPal, crypto, bank transfer) varies as new providers, fee structures, and regulations emerge.
- Payment infrastructure — logging, fraud detection, and notification vary with performance, security, and observability demands.
These are not the same change driver. Under the interpretive claim that these two forcing conditions vary independently, IVP assigns — the payment-method element’s change-driver set differs from the infrastructure’s. A compliant partition therefore puts them in different modules.
Now the mechanism matters. class CreditCardPayment extends Payment does not merely reuse code. It makes the subclass embody the base class’s knowledge: everything Payment references — the logging, fraud-detection, and notification services — becomes knowledge the subclass carries with it. That is the crux. Those knowledge pieces are not part of the payment-method’s causal domain knowledge. The payment-method domain does not require the element that validates a card and settles a charge to also embody how transactions are logged, fraud is screened, or notifications are sent. By inheriting them, CreditCardPayment embodies knowledge its causal domain does not mandate — accidental embodiment — and that embodiment drags with it coupling to infrastructure the domain never required it to share.
That coupling connects elements assigned different sets of change drivers and is required by no domain artifact — it is cross- accidental coupling, which IVP’s minimal coupling forbids. This is the precise defect IVP identifies in the inheritance hierarchy. The verdict is not “you used inheritance” — it is “this mechanism made the element embody knowledge outside its causal domain, and that accidental embodiment became accidental coupling.”
A mechanism choice is therefore a faithfulness check on domain knowledge. When the domain already dictates that payment-method knowledge and infrastructure knowledge are separate bodies of causal knowledge, the correct structure is derived directly from that structure: separate elements in separate modules. Inheritance that blurs the boundary adds nothing the domain had not already decided. If you cannot tell whether the extends coupling is domain-required, you have not found that inheritance is wrong — you have found that your causal domain knowledge is incomplete.
4 Why composition avoids that specific defect
The composition version:
interface PaymentMethod {
void validate();
PaymentResult executePayment();
}
class CreditCardPayment implements PaymentMethod {
public void validate() { /* credit card validation */ }
public PaymentResult executePayment() { /* credit card processing */ }
}
interface FraudDetector { boolean analyze(PaymentMethod m); }
interface TransactionLogger { void log(String d); }
interface NotificationService { void send(String m); }
class PaymentProcessor {
private final FraudDetector fraudDetector;
private final TransactionLogger logger;
private final NotificationService notifier;
PaymentProcessor(FraudDetector f, TransactionLogger l, NotificationService n) {
this.fraudDetector = f; this.logger = l; this.notifier = n;
}
void process(PaymentMethod method) {
method.validate();
if (fraudDetector.analyze(method)) {
PaymentResult r = method.executePayment();
logger.log("Payment processed: " + r);
notifier.send("Payment confirmation");
}
}
}Here CreditCardPayment couples to the infrastructure only where the domain requires it. The PaymentProcessor composes the two variation domains — but the payment-method element and the infrastructure services are separate elements, coupled through interfaces only where a genuine causal dependency exists. Nothing forces the payment-method class to absorb the infrastructure’s dependency footprint.
The point is not that composition is magic. The point is narrower: composition lets the developer form the coupling at the points the domain mandates, whereas inheritance’s extends edge couples the subclass to the base’s whole dependency footprint transitively, mandated or not. IVP does not endorse composition — it diagnoses the coupling a mechanism actually produces against its -partition.
5 What IVP does not say
IVP does not endorse “prefer composition over inheritance” as a universal rule. Three specific caveats:
Inheritance is not forbidden. Where the two sides genuinely share the same change driver — so IVP assigns them the same and the same module — the coupling inheritance creates is essential, not accidental, and IVP is satisfied. The composition example itself uses this: CreditCardPayment and PayPalPayment implement PaymentMethod, an “is-a” relationship to an abstraction that genuinely shares the payment-method change driver. That inheritance is IVP-compliant. What the example removes is inheritance across the two change drivers — subclassing a base that pulls in infrastructure the payment-method domain does not mandate. A geometric-shape hierarchy is IVP-compliant if all its elements are -equivalent, so that every change modifying one also creates a modification requirement for the others. A framework’s intentional extension points are IVP-compliant only when the extension and the base are same-; a low activation frequency does not by itself make them compliant. The defect is coupling different change drivers, not using inheritance.
Composition can violate IVP too. Composition that couples elements assigned different sets of change drivers across a module boundary is just as much cross- accidental coupling. Mechanism alone does not determine compliance; the change-driver assignment does.
The verdict is rule-relative. “The hierarchy couples independent change drivers” is an IVP-specific diagnosis. It follows from choosing -equivalence as the modularization rule. Another rule would partition the elements differently and classify the same edges differently. IVP’s answer is the answer given its choice of rule, not a mechanism-independent fact.
6 The takeaway
IVP does not tell you to reach for composition and avoid inheritance. It tells you something more useful: start with the domain. Ask what the domain actually prescribes: what are the change drivers, and what does each element’s specification require it to embody and depend on? The change drivers determine the partition; then couple elements only where the domain requires the dependency, and never make an element embody knowledge its causal domain does not mandate. When you evaluate a design, ask not “inheritance or composition?” but “what does the structure of the causal domain knowledge dictate, and does this mechanism add coupling the domain never had?”
In the payment example, inheritance happens to manufacture accidental embodiment and cross-driver accidental coupling — through the knowledge the extends edge forces the subclass to carry — and composition happens to avoid it. That is why the classical guideline points the right way in this example. But IVP explains the reason: the best structure is derived directly from the structure of the causal domain knowledge, and the mechanism is faithful exactly when it adds no coupling the domain did not already decide.
7 Learn More
IVP formalizes the module partition a system’s change drivers prescribe and the coupling a compliant partition tolerates — giving a precise, decidable answer where classical heuristics rely on intuition. The formal development — the module partition and the coupling-floor argument — is proved in the paper on the Independent Variation Principle [1], with the change-driver partition and purity/completeness accounted for in Causal Cohesion [3] and the cohesion-as-correctness framework in On the Nature of Cohesion [2].
This site’s overview sections — IVP (software) and Software design — present the theory and its applications in more depth, and the language coupling analysis applies the coupling lens across 65 programming languages.