Skip to main content

Encapsulate what varies

Identify the aspects of your application that vary and separate them from what stays the same.

The goal is damage control. Picture the program as a ship and changes as mines floating under the surface. A single-compartment hull sinks on the first hit. Divide the hull into sealed compartments and a hit costs you one compartment, not the voyage.

Why it exists

Every class mixes parts that are stable for the life of the program with parts that a legislature, a competitor, or a product manager can change on a whim. Leaving both in the same method means every change to the volatile part risks the stable part, and every review of the stable part has to account for logic that has nothing to do with it.

At the method level

Somewhere in an e-commerce codebase, getOrderTotal sums the line items and applies tax. The summing part is arithmetic and will outlive us all. The tax part depends on country, state, sometimes city, and on legislatures. Two lifetimes, one method.

// Tax rules and order arithmetic, sharing one method.
method getOrderTotal(order) is
total = 0
foreach item in order.lineItems
total += item.price * item.quantity
 
if (order.country == "US")
total += total * 0.07 // US sales tax
else if (order.country == "EU")
total += total * 0.20 // European VAT
 
return total
 
// The rate depends on country, state, sometimes city, and
// changes whenever a legislature feels productive. Every one
// of those edits lands in a method whose name says it only
// cares about the total.
// The volatile part now has its own front door.
method getOrderTotal(order) is
total = 0
foreach item in order.lineItems
total += item.price * item.quantity
 
total += total * getTaxRate(order.country)
 
return total
 
method getTaxRate(country) is
if (country == "US")
return 0.07 // US sales tax
else if (country == "EU")
return 0.20 // European VAT
else
return 0
 
// Tax changes are isolated in one method. And when the rules
// outgrow it - state, city, product category - it lifts out
// into a class without touching the arithmetic.

At the class level

Methods accumulate. The tax rule that was one if becomes a rate table, then per-product categories, then exemptions - each with its own helper fields and methods, all of them blurring what the Order class is supposed to be about. When the volatile part has grown its own gravity, give it a class.

Order- lineItems- country- state- city...20+ fields+ getOrderTotal()+ getTaxRate(country, state, product)
BEFORE: tax is calculated inside the Order class.

Objects of the Order class delegate all tax-related work to an object that does just that.

Order- taxCalculator- lineItems- country- state- city...20+ fields+ getOrderTotal()TaxCalculator...+ getTaxRate(country, state, product)- getUSTax(state)- getEUTax(country)- getChineseTax(product)total = 0foreach item in lineItems subtotal = item.price * item.quantity total += subtotal * taxCalc.getTaxRate(country, state, item.product) return total
aggregation
AFTER: the order still knows its total. It no longer knows how tax works.

Try it yourself: shipping cost is the next candidate - it depends on weight, destination, and a carrier's changing rate card, none of which Order should know about. Sketch the class you would extract for it, using TaxCalculator above as the template.

The cost of overapplying it

Walling off a part of a class that is not actually going to vary buys nothing but an extra hop to read through. getTaxRate() earned its extraction because tax law genuinely changes on its own schedule; extracting a getShippingLabelTextCalculator() for a label format that has been stable for three years and nobody has proposed changing is indirection paid for with no matching benefit.

How it relates to its neighbours

Separation of Concerns is this same instinct at a larger grain - Encapsulate What Varies operates inside one class, walling off a volatile part from a stable one; Separation of Concerns operates across an entire program, splitting independent questions into independent objects regardless of whether either one is "volatile." And YAGNI is the principle that decides when this page's advice applies: encapsulate the moment a real axis of variation exists, not the moment you can imagine one.

Where you'll see it in the pattern catalog

This is, almost word for word, the intent line of every creational pattern in this catalog: Factory Method encapsulates which concrete product gets created; Strategy encapsulates which algorithm runs; State encapsulates which behavior applies for the current mode. Once you can name the exact thing that varies in a design, you can usually name the pattern that encapsulates it.

Check yourself

Question 1 of 3

getOrderTotal() computes a line-item sum and applies a tax rate that depends on country and legislature. What is the encapsulate-what-varies move here?