11.2 Low Coupling and High Cohesion — The Yin and Yang of Software Design
Low coupling and high cohesion are the two evaluator principles for every design decision that follows. They appear in early architectural thinking as box diagrams with fan-in and fan-out, long before object orientation, and they remain the scoreboard for all nine GRASP patterns. The lecture frames them as yin and yang: complementary, not independent, and best understood together.
Hook — why do two slogans deserve a full section? Because almost every later argument in this lecture reduces to one sentence: does this alternative raise coupling or lower cohesion, and is the trade worth it? If you cannot answer that sentence with numbers and examples, the later patterns (polymorphism, indirection, fabrication, protected variations) become recipes without taste. Master these two yardsticks and the rest of GRASP is a consequence.
Intuition and analogy — coupling as string, cohesion as gravity. Picture each class as a person in a workshop. Coupling (how much one person depends on another to do their job — phone calls, borrowed tools, shared notes) is like string between people. More string means more tug when one moves. Cohesion (how related the jobs are that one person does — are all their tasks about baking, or do they bake, fix plumbing, and file taxes?) is like gravity inside one person: strong internal gravity keeps related work together; weak gravity scatters it. You want thin string between people so one person's stumble does not drag others, and strong internal gravity so each person's work makes sense on its own.
Where the analogy breaks: in a workshop, tight teams sometimes need thick string; in software, the cost of string is paid every time you change code, even if day-to-day work feels collaborative.
11.2.1 What Coupling Means — Dependence Between Classes
Formal idea — coupling as dependence. Coupling is the degree to which one class or module depends on another to do its work. In the box-diagram language used before object orientation, coupling is the number and strength of lines between boxes. Two derived counts make it visible:
- Fan-out (how many other boxes a given box talks to) — outgoing dependence.
- Fan-in (how many other boxes talk to a given box) — incoming dependence.
Both are readable directly from a class or interaction diagram. A class with fan-out 6 depends on six collaborators; a change in any of them may require a change in it. A class with fan-in 9 is depended on by nine clients; a change inside it may ripple to nine places. Good designs keep both in a useful middle range.
For any pair of classes and , an informal dependence measure is used in the session:
where denotes the likelihood that a change propagates. The verbal teaching point alongside the expression is that low dependence should be the goal, and independence of modules is the ideal direction — not absolute independence (which would mean no collaboration at all), but minimal necessary knowledge.
Low coupling means low dependence. A class that knows little about others can be understood, tested, and reused with less surrounding code. High coupling means high dependence, and the session repeats without hedging that high coupling is not desirable: understanding becomes complex, reuse drops, and change becomes expensive because a change in one class forces changes in the classes that depend on it.
A small numerical sense-check: suppose Register with fan-out 6 knows ProductDescription, Pricing, Inventory, ReceiptPrinter, CashDrawer, and Payment. If each of those changes once per release with independent probability 0.2 of affecting Register's interface, the chance Register escapes untouched is — it is touched of releases. Reduce fan-out to 2 (Register knows only Sale and a Printer interface) and the untouched chance rises to — touched only of releases. Fewer lines, fewer surprise edits.
11.2.2 What Cohesion Means — Related Responsibilities Inside a Module
Formal idea — cohesion as relatedness of responsibilities. Cohesion is the degree to which responsibilities inside one module belong together. A highly cohesive class or component groups strongly related tasks and information and serves a single well-defined purpose; a low-cohesion class mixes unrelated data and methods. The session's test phrase is single-purpose components whose responsibilities are related.
Concretely, a class with methods is cohesive when those methods operate on shared attributes and support one responsibility family. Write it as: methods of share attributes and jointly realize responsibility . When is "manage a Sale's line items and total," methods like addLineItem, getTotal, and getLineItems cohere; when a class mixes saveToDatabase, sendEmail, and parseCommandLine, cohesion is low even if each method works in isolation.
A highly cohesive class supports good data encapsulation: the data needed for a task lives with the behavior that uses it, so the behavior does not chase data through long chains. It also lowers the cost of understanding, because a person can grasp a small, focused component on its own, whereas a large class that mixes unrelated jobs forces a reader to hold many ideas at once. The session links single-purpose, small components to lower complexity, but also warns that size alone is not cohesion — ten unrelated one-line methods are still low-cohesion if they share no purpose.
11.2.3 Architectural Roots — Fan-In, Fan-Out and Box Diagrams
Before object orientation, designers divided a solution into blocks and drew a box-and-line diagram showing which block talks to which. Fan-in counted incoming uses; fan-out counted outgoing uses. The same idea maps unchanged to classes and objects, and the session retains this vocabulary as a practical way to assess coupling directly from UML.
A class with high fan-out depends on many others — it is vulnerable to their changes. A class with high fan-in is depended on by many others — its own changes are risky because they ripple outward. Both views predict change impact, and both are visible in diagrams without running code. During review, drawing the boxes for Register, Sale, and Payment and counting lines already tells you whether a design is trending toward brittle wiring before you write a test.
Tiny fan-in/fan-out count — applying it to POS. Consider two sketches for handling a new sale.
- Sketch 1: Register (fan-out 5) talks to Sale, ProductDescription, Pricing, Inventory, and ReceiptPrinter directly. Change pricing rules and Register must be revisited because Register knows Pricing's interface.
- Sketch 2: Register talks only to Sale (fan-out 1) and an abstract Printer; Sale talks to line items. Sale carries fan-out 2 to ProductDescription and Pricing. No class has fan-out above 2, and fan-in stays moderate (no God object with fan-in 15). The second sketch will survive pricing changes with fewer edits. Sense-check: count lines before you count classes — fewer lines usually predicts fewer surprise edits.
11.2.4 The Trade-Off — More Classes, More Cohesion, But More Coupling
High cohesion and low coupling can pull in opposite directions, so the designer must balance them. This is the central teaching point of the subsection and the pivot for every later GRASP debate.
The core trade. Alternative A: about twenty classes, each with a single, related purpose — highly cohesive, easy to understand in isolation. Alternative B: about ten classes, but five or six of them hold a lot of unrelated data and methods — lower cohesion, fewer collaborations. Which is better? The session answer is that Alternative A is generally preferred, because high cohesion helps understanding and reuse — a small focused class is easier to name, test, and reuse.
Now add coupling. With twenty highly cohesive classes, one piece of work (for example, completing a sale) may need interaction across four or five classes — interdependence for one job increases, so coupling rises. With fewer, broader classes, one job may stay inside one object, so coupling falls, but cohesion falls with it because that one object now does many unrelated things. The key word repeated in the session is balance: do not chase one principle to an extreme. Always ask what is gained and what is lost.
Another balance lens is reuse. More small, focused classes can be reused more widely — a pricing rule that lives in its own small class can be reused by POS, e-commerce, and reporting. But too many tiny classes with many links raise the number of dependencies, and dependence again hurts reuse: a class that needs six collaborators to do its one tiny job is not reusable without dragging all six along. The designer tries to keep both qualities in a useful middle band, not at extremes.
Real-world echo: component-based and micro-service teams face the same sprint-level trade: splitting a service improves focus but adds wiring, monitoring, and versioning cost. The GRASP scoreboard is the same at every scale.
Visual intuition: imagine an x-y plot with cohesion on the vertical axis (higher is better) and coupling on the horizontal axis (lower is better). Twenty cohesive classes sit high but to the right (higher cohesion, higher coupling); ten mixed classes sit low and to the left (lower cohesion, lower coupling). The sweet spot is the upper-left quadrant — high cohesion, low coupling — but pushing any further right or down moves you out of it. The one-sentence takeaway: the best designs live in the upper-left, and every split or merge moves you on this plot.
Assumptions and scope — when this trade-off matters. This balance analysis assumes a system with a life cycle where change is expected. It assumes you can see fan-out in diagrams and that classes are the unit of change. If your unit of change is a service or a module, replace "class" with that unit — the same curves apply. If your system has no expected change (one-off script), neither axis matters much and the simplest sketch wins.
11.2.5 Low Coupling Enables Reuse and Easier Maintenance
Why low coupling pays — reuse, the formal GRASP wording, and the maintenance chain. A concrete gain from low coupling is reuse. A routine that depends on little else — for example a print(value) that takes only a value to format — can be called from Sale receipt printing, from nightly reports, and from debugging tools in current and future projects. If printing were tangled with sale calculation and persistence, reuse would require dragging those coupled classes along, and the routine would stay where it was born.
The consequences of high coupling are presented as a causal chain: harder to understand (a reader must keep many related classes in mind at once) → harder to reuse (using a class forces inclusion of every class it depends on, even when not needed) → more complex system graph (many strong edges) → worse performance (traversing long chains of object connections adds cost) → difficult maintenance (changes propagate) → lower reuse and therefore lower productivity over time because quality classes are not portable. For high cohesion the chain is the mirror: good encapsulation, smaller focused pieces that manage complexity, easier change.
The formal wording for low coupling in the GRASP collection is preserved exactly as in the session: encourage assigning responsibility so that coupling stays low, supporting relatively independent and more reusable classes. Reuse then raises productivity because the team reuses quality solutions and patterns, not just code.
Scope — the anti-over-engineering caution. Do not overdo the quest for reusability. Designing for every future reuse can raise project cost now — more interfaces, more indirection, longer build times. If a design currently has no coupling problems and meets its performance and maintenance goals, further decoupling is not required. Moderate coupling is always present, because an object-oriented system is by definition a collection of communicating objects, and the world itself is interdependent. The useful rule is to assess the current level of coupling and only reduce it when it causes a concrete problem: a change that repeatedly touches many classes, a class that cannot be reused without five collaborators, or a test that needs a whole subsystem to run. There is no design that is best on every dimension; every pattern choice has pros and cons that must be traded off.
Visual intuition: picture a dependency graph drawn as dots (classes) and arrows (depends on). A high-coupling graph looks like a dense spider web — tug one strand and the whole web shivers. A low-coupling graph looks like islands connected by a few narrow bridges — a storm on one island stays there. The takeaway: the sparser the bridges, the cheaper the storm.
11.2.6 Worked Examples — Evaluating Coupling in Design Alternatives
Example 1 — Who creates Payment: Register versus Sale? (Coupling and Expert together). Question: which class should be responsible for creating a Payment instance, the Register (the point-of-sale terminal / controller) or the Sale?
Solution A — Register creates Payment: Register computes or collects amount, instantiates Payment(amount) and attaches it to Sale via sale.addPayment(p). This mirrors the real world at first glance — the cashier acts at the register. Coupling count: Register must know amount, line items, discount rules, and Payment construction details. Fan-out of Register grows by at least 2 (knows Sale and Payment construction protocol).
Solution B — Sale creates Payment via makePayment: Sale already holds items, total, and discounts. It exposes makePayment(cashTendered) which internally does new Payment(total, cashTendered). Register simply calls sale.makePayment(cashTendered). Coupling count: Register knows only Sale's one operation; Sale, which already knows its own data, creates Payment.
Evaluation: Solution B has lower coupling and higher cohesion. Register avoids unnecessary knowledge of amount and construction details. Sale stays the information expert — all data needed for the payment already lives with Sale — and cohesion improves because payment creation, which is logically part of completing a sale, lives with the sale. Register stays light, as controller classes should. Verdict: Solution B wins. In the manifest's terms, this is an essential application of Expert + Creator + Low Coupling + High Cohesion.
Trace with numbers: Sale has two line items: item A quantity 2 at 50 each, item B quantity 1 at 30. Total = 130. Cash tendered = 150. In Solution B, sale.makePayment(150) internally computes total 130, creates Payment{amount:130, tendered:150, change:20}. Register never sees line items. In Solution A, Register would need to call sale.getTotal() (130), compute change, then new Payment(130,150) — Register now knows the total protocol.
Example 2 — Point-of-sale terminal should not talk to every class (fan-out control). Bad design: Register or the Point-of-Sale Terminal talks directly to ProductDescription, PricingStrategy, Inventory, ReceiptPrinter, CashDrawer, and Payment — fan-out 6, with Register as a God object. Every external change (new printer model, new pricing rule) requires opening Register. Fix: apply Low Coupling + Controller discipline — let Register delegate to Sale for everything sale-related, and let Sale own pricing. Register's collaborators shrink to Sale and abstract device interfaces (Printer, Drawer). The teaching phrase preserved verbatim: the terminal should not have knowledge of any more classes than it has to. Each removed direct link is one fewer reason to edit Register next release.
Before vs after wiring:
- Before: (6 edges)
- After: (3 edges), (3 edges but isolated from Register)
Coupling is not reduced globally to zero — it is redistributed so no single client is fragile.
Example 3 — Reuse through low dependence (the print routine). To make reuse concrete, contrast two signatures:
- Low-coupling version:
print(value: Money)depends only on its argument. Calls:print(sale.getTotal())from Sale,print(report.getTotal())from NightlyReport,print(debugBalance)from diagnostics — same routine, three contexts, zero extra collaborators. - High-coupling version:
printTotalForSale(sale: Sale)reaches into Sale attributes and formatting rules. It can only be used where a full Sale exists and it now depends on Sale's internal structure. It cannot print a report total.
Sense-check with a count: the first version has coupling degree 1 (depends on Money value only); the second has coupling degree 3 (depends on Sale structure plus formatting plus value). Lower degree predicts wider reuse, and the prediction is borne out when the same routine is lifted into a second project without bringing Sale along. Final answers bolded: the low-coupling print(value) is reusable across receipts, reports, and logs; the high-coupling printTotalForSale is not portable.
11.2.7 Student Questions and Answers
Q: What do you mean by high cohesion and low coupling? A: Think of dependence versus focus. Low coupling is low dependence on other modules. A class should need little help from others to do its job — few incoming or outgoing lines in the box diagram. High cohesion is high relatedness inside one module. All the methods and data inside a module should serve one purpose or a tightly related set of purposes, so the component is single-purpose and can be understood alone. Both ideas together are the guiding principles for evaluating any design — one measures thickness of string between objects, the other measures strength of gravity inside one object.
Q: Do many small, highly cohesive classes always beat a few larger classes with mixed responsibilities? A: In general, many small, single-purpose classes are preferred, because they raise cohesion and reuse. But there is a balance point, and this is a common misconception the session corrects. With many classes, one operation may need several collaborating objects, so coupling goes up. With fewer mixed classes, one operation may stay inside one object, but cohesion drops and understanding suffers — the large class does many unrelated things. The right answer is to balance the two and to judge the current design: if fan-out is already high and operations scatter across many classes, adding more classes may hurt; if cohesion is low and a God object dominates, splitting helps. Judge the graph, not the rule.
Q: Can we have zero coupling? Is it possible? A: This is the zero-coupling misconception, and the session addresses it directly. Zero coupling between classes would mean classes never interact — no calls, no shared types, no dependencies. In the limit, one class does everything, so inter-class coupling is zero in a narrow numeric sense, but the system is not object-oriented at all. An object-oriented system is by definition a collection of communicating objects, so some dependence is required. The correct goal is moderate, low coupling, not zero. Even the world depends on collaboration — a sale needs a product and a payment — so trying for zero dependence misses the point of the paradigm. Think low, not none. Several students asked this, so the correction is emphasized: prefer the term moderate low coupling and count fan-out before claiming decoupling is needed.
Q: How does low coupling increase reuse? Can you give an example? A: If dependence is low, a class can be lifted and used in many contexts without pulling its collaborators along. The session's canonical example is a print routine that only needs a value to print — print(value) depends only on Money, so it can be reused for receipts, reports, and logs in this project and in future projects. A routine that reaches into many other objects (for example, printTotalForSale that digs into Sale, discount, and formatting objects) cannot be reused without including those objects, so its reuse stays low. The test for reusability is literal: can you copy this class into a new project without copying five others? If yes, coupling is low enough.
Q: In the Sale and Payment example, why is Sale the better creator? A: Sale holds all the related information for a payment — the items, the total, and the rules for calculation — so it is the information expert. Assigning makePayment to Sale keeps cohesion high (payment creation is logically part of completing a sale) and coupling low (Register does not need to know amount, line items, or construction details). If Register creates Payment, Register becomes burdened with too many responsibilities, which controller classes should avoid. Register should delegate to Sale and stay focused on coordinating system events. This is also a Creator pattern argument: Sale closely uses, contains, and has initializing data for Payment.
Real-world note: teams that track fan-in and fan-out in code reviews — flagging any class whose fan-out jumps by 2 in one pull request — often catch rising coupling before it becomes a maintenance burden. Tools that compute these counts from imports or sequence diagrams make the GRASP judgment quantitative.
Recap and bridge. Low coupling (thin string between classes, low fan-out/fan-in) predicts cheap change and good reuse; high cohesion (strong internal gravity, single-purpose responsibilities) predicts understandability and encapsulation. They trade off, so the designer balances them case by case and only decouples when a concrete coupling problem exists. That scoring mindset is the bridge to GRASP: the first five patterns (Expert, Creator, Controller, Low Coupling, High Cohesion) are the opening placements, and the next four (polymorphism, indirection, fabrication, protected variations) are the repairs when those placements alone hurt the score.
Exam note: expect to compare two class assignments for the same task (for example, who creates Payment, who calculates total) and to justify the better one by naming changes in coupling, cohesion, reuse, and fan-in/fan-out, with a small numeric count to make the argument concrete.
11.3 GRASP — The Nine Responsibility-Assignment Patterns
GRASP stands for General Responsibility Assignment Software Patterns — not a library you import, but a set of named guidelines for deciding which class should do which work. The session organizes them as five core patterns you already know, plus four additional patterns that resolve conflicts when the first five pull you in opposite directions.
Hook — why nine names for one job? Because "assign work to the right class" is too vague to argue about. Nine sharp names turn a vague feeling ("this feels wrong") into a pointed claim ("this violates Expert and hurts cohesion — let's try Pure Fabrication instead"). The hook is that vocabulary makes design review fast enough to happen at all.
Intuition and analogy — GRASP as a toolbox, not a lawbook. Think of the nine patterns as a toolbox with nine tools. Expert and Creator are your screwdriver and hammer — you reach for them first for most placements. Low Coupling and High Cohesion are the measuring tape — you hold them up to every placement to see if it is straight. Controller is the clamp that holds the work while you place it. When those five leave you in a bind (for example, Expert would make one class huge), the four specialty tools — Polymorphism, Indirection, Pure Fabrication, Protected Variations — pry the design apart cleanly. You would not hammer a screw, and you would not reach for Protected Variations before you have checked Expert. Order matters.
Where the analogy breaks: tools do not interact; patterns do — polymorphism often is a pure fabrication, and protected variations often uses polymorphism plus indirection together.
11.3.1 The First Five — Expert, Creator, Controller, Low Coupling, High Cohesion
The five you already own — precise definitions with the POS anchor. Each pattern below is stated as problem → solution, with the point-of-sale anchor that the lecture reuses so the contrast is visible.
- Expert (Information Expert) — Problem: which class should own a responsibility? Solution: assign it to the class that has the information needed to fulfill it. Anchor: Sale is the expert for payment information (it holds items, total, discounts), so Sale creates Payment via makePayment. Placing it elsewhere spreads information.
- Creator — Problem: who should create instance ? Solution: assign class the creation responsibility if contains, aggregates, records, closely uses, or holds initializing data for . Anchor: Sale contains SalesLineItems and has the total for a Payment, so Sale is the Creator for both.
- Controller — Problem: what first object beyond the UI layer should receive and coordinate a system event (for example, enterItem, makeNewSale, makePayment)? Solution: assign to a controller representing the overall system, device, or use-case handler (Register, ProcessSaleHandler) — but keep the controller light so it does not become low-cohesion. Anchor: Register receives the cashier's enterItem but delegates calculation to Sale; it coordinates, not calculates.
- Low Coupling — Problem: how to judge and keep dependence low? Solution: keep fan-out and knowledge low so a change in one class is less likely to force change in another. Scored by counting dependencies before and after the move.
- High Cohesion — Problem: how to keep a class focused? Solution: keep responsibilities inside one module related so the module serves a single well-defined purpose and can be understood alone. Scored by asking whether the methods share data and serve one responsibility family.
These five are complementary, and a decision that lowers coupling often raises cohesion at the same time — giving payment creation to Sale both thins Register's dependencies and keeps Sale's purpose focused on completing sales. That double win is why Expert + Creator + Controller are usually tried first.
11.3.2 The Next Four — Polymorphism, Indirection, Pure Fabrication, Protected Variations
The four that repair conflicts — each solves one specific bind where the first five alone would hurt the scoreboard.
- Polymorphism — Problem: alternatives vary by type and you want pluggable components. Solution: handle type-based alternatives through a shared interface's polymorphic operation, not through switch/if chains. New variations are added as new classes outside existing code. Anchor: one ITaxCalculator interface with getTaxes, and TaxCalculator2020, TaxCalculator2021, ThirdPartyAdapter each provide their own implementation.
- Indirection — Problem: two elements are too tightly coupled to be left talking directly. Solution: insert a new intermediate object or layer to mediate, so they depend on a small stable mediator rather than on each other's full detail. Anchor: a PricingService or TaxAdapter mediates between Sale and external pricing engines.
- Pure Fabrication — Problem: keeping a responsibility with the Expert would make the expert too large, too coupled, or would duplicate logic across many experts. Solution: invent a new class that does not exist in the domain purely to group a highly cohesive set of responsibilities for reuse. Anchor: PersistentStorage for save/update/delete, TableOfContentsGenerator for document generation — neither was named by the domain, both group one coherent behavior.
- Protected Variations — Problem: you need stability at points of predicted variation so change does not ripple. Solution: identify those variation points (behavior, data, hardware, operating system) and protect them with stable interfaces so extension does not require modification. This is the umbrella under which the Law of Demeter (don't talk to strangers through long chains) and Liskov Substitution (subtypes must honor supertype contracts) sit as concrete techniques. Anchor: ITaxCalculatorAdapter behind which any vendor's calculator can be substituted without touching callers.
The formal relationships the session states: Protected Variations is the most general idea; polymorphism and indirection are concrete ways to achieve it; pure fabrication is often the class that carries the indirection; Liskov and Demeter are the two stability checks inside protected variations.
When to reach for each: if Expert suggests putting behavior where the data lives and that placement keeps the data holder focused and lightly coupled, stop — Expert is enough. If Expert would make that holder huge or would tie it to an external API, try Pure Fabrication. If two elements already talk through many direct lines, insert Indirection. If alternatives multiply by type (yearly tax rules, multiple vendors), use Polymorphism. If you worry about future change breaking callers, frame the answer as Protected Variations and pick the mechanism that hides the varying decision.
11.3.3 How the Nine Fit Together
The decision pipeline — one aim, one scoreboard, then repairs. All nine patterns serve one aim: place responsibilities where they cause the least harm to change and the most gain for reuse. The scoreboard is always low coupling and high cohesion.
The pipeline the lecture implies is: Expert and Creator suggest the first placement → Controller shapes the entry point for system events → Low Coupling and High Cohesion score that placement → when the score is bad (the expert would become incohesive or highly coupled), restructure with Polymorphism, Indirection, or Pure Fabrication → shield the restructured collaboration from future change with Protected Variations (Liskov + Demeter + Open-Close). That is why the lecture presents the final four as "the focus of the current discussion" — they are the answers to hard cases the first five cannot settle alone.
Visual intuition: draw a flowchart where Expert asks "who has the information?" and draws an arrow to a candidate class. A diamond below asks "does that class stay cohesive and loosely coupled?" If yes, you are done. If no, three repair arrows branch out: "does behavior vary by type? → Polymorphism", "are two elements too tightly wired? → Indirection", "does no domain class fit? → Pure Fabrication". All three repair arrows converge into a shield labeled Protected Variations that wraps the varying part behind a stable interface. The one-sentence takeaway: the first five propose, the next four repair, the last one protects.
Pitfalls — misreading GRASP scope.
- Treating GRASP as laws rather than scored guidelines. Every pattern choice has pros and cons; the session repeats that there is no design that is best on every dimension — you trade scope against flexibility.
- Reaching for Pure Fabrication before trying Expert. Most objects still come from the domain through Expert; fabrication is the remedy for the specific failure where Expert would create a God object or duplicate persistence across many classes.
- Confusing protected variations with "protect everything." Only predicted, worthwhile variation should be shielded — the lecture explicitly warns against speculative generalization that overloads the system and drops performance.
Recap and bridge. The nine GRASP patterns are a named pipeline: five core placements that you try first, scored by coupling and cohesion, and four repair patterns that restructure and shield those placements when the score is bad. The bridge forward is that the next four sections examine each repair in depth — starting with polymorphism, which handles the most common repair case: variations that differ by type.
Exam note: be ready to name all nine, but more importantly to walk through the pipeline on a concrete POS task (for example, where does getTaxes live?) and to say which pattern you tried first, why it hurt the scoreboard, and which repair you applied.
11.4 Polymorphism — Handling Alternatives Without Conditionals
Polymorphism looks simple as a language feature — "same name, many implementations" — but as a design pattern it solves a hard product problem: how to support a family of related behaviors that change by type (yearly tax rules, multiple printer vendors, multiple shape kinds) without hard-coding every choice in a growing chain of branches that must be hunted and edited every time a new variant arrives.
Hook — what happens next January? Tax rules change every year. A next-generation point-of-sale terminal must integrate with three external tax calculators today and two more next year, each with a different wire protocol. If the code says if year==2020 then calc2020 else if year==2021 then calc2021 in five places, January means opening five files. Is there a way to add a new year's rules by adding one file and touching none of the old ones? That is the hook polymorphism answers.
Intuition and analogy — the socket and the USB port. Picture an electrical socket (a stable interface) in the wall. The socket's shape — two or three holes, one voltage contract — never changes. Behind it, a fan, a bulb, or a heater each draws power differently, but the wall side is unchanged. Swap the device, not the wall. The USB analogy is the digital twin: one USB port accepts a keyboard, a mouse, or a hard disk through one contract. The host does not need a new slot per device; the port is the stable interface, each device is a pluggable implementation.
Mapping explicitly: socket/USB port = the polymorphic interface (for example, ITaxCalculator with getTaxes); each device = a concrete class (TaxCalculator2020, ThirdPartyAdapterForVendorA); plugging in = runtime dynamic binding where the actual object type selects the code; wall/host = the client (Sale, Register) that depends only on the port. Where the analogy breaks: a socket enforces shape physically; a software interface enforces it by type contract and tests. Forgetting to honor the contract is the software equivalent of wiring a 110V heater into a 220V socket — it fits mechanically but violates the electrical contract, and this is exactly what Liskov will later police.
11.4.1 Definition — One Interface, Many Implementations
Formal definition — polymorphic assignment of responsibility. Polymorphism means many forms. In GRASP, it means: when related alternatives or behaviors vary by type (class), assign responsibility for the behavior — using a polymorphic operation — to the types for which the behavior varies. The program gives the same name to similar services (draw, getTaxes, findArea) and each concrete type provides its own method for that name. Dynamic binding (runtime dispatch) then selects the right implementation based on the actual object passed, so explicit type checking by the caller is not needed.
The verbal description preserved from the lecture for reconciliation is: alternatives based on type should be handled through a polymorphic operation, not through conditional logic. When related alternatives or behaviors vary by type, the design should rely on overriding and runtime dispatch, not branches.
By this definition, polymorphism is not just inheritance syntax; it is a responsibility-placement rule. The question to ask during design is: does this behavior have a family of type-dependent variants? If yes, put the polymorphic operation on the family members, not in the client.
11.4.2 Pluggable Components — The Electrical Plug and USB Analogy
A pluggable component (a part swappable through a common interface without changing the client) is exactly what polymorphism provides. The client sees one stable interface such as a tax calculator and the server side allows new implementations to be plugged in without changing client source or recompiling the client binary.
The unified development process and component-based software engineering aim for this quality systematically: expose one payment gateway interface and plug in Stripe, PayPal, or Apple Pay adapters behind it. The session uses both the electrical plug and the USB port because they stress complementary points — the wall socket stresses one physical shape, many behaviors behind it, while USB stresses one protocol family accepting an open-ended set of future devices. Both point to the same design consequence: closed for modification, open for extension at the client.
Real-world anchor: teams that ship POS terminals to multiple countries reuse this analogy when onboarding newcomers: "think of each country's tax rules as a different plug adapter behind the same wall socket."
11.4.3 Why Conditionals Fail — If-Then-Else and Switch Versus Polymorphism
Comparison — conditional dispatch versus polymorphic dispatch.
| Dimension | Conditional (if/switch) | Polymorphism (interface + overriding) | ||
|---|---|---|---|---|
| New variant | Edit the client — add a branch inside existing code in every place that switches on type | Add a new class outside existing code that implements the same interface; client unchanged | ||
| Number of edit points | Equal to number of conditionals scattered through the codebase (often 5-10) | One — the new class | ||
| Knowledge in client | Client knows every variant name and selection rule (high coupling to each) | Client knows only the interface (low coupling) | ||
| Risk | Forgetting one branch silently miscounts; combining predicates with &&/` |
` hides branches | Compiler enforces interface implementation; missing variant fails loudly at registration time | |
| Example | if year==2020 then calc2020 else if vendor==A then calcA |
calculator.getTaxes(sale) where calculator is ITaxCalculator |
End with the one-sentence when-to-pick-which that the session requires: when the set of alternatives is fixed and small, a conditional is simpler; when alternatives are predicted to vary by type — yearly rules, multiple external adapters, shape families — prefer polymorphism so new variations are created from outside by adding a new class, not by inserting another branch inside.
Conditional variations have the concrete form repeated in the session: if year is 2020 then use these tax rules, else if year is 2021 then use those rules, else switch on product type. For a fixed, closed set this is the simplest thing that works. For an open, product-lifetime set it is brittle: each new variation forces a change inside the fixed conditional, the client must be edited, tested, and redeployed, and the edit cost repeats yearly. In real systems, that switch is not in one place — it is copied into every function that does something slightly different (draw, drag, stretch, delete), so adding a new shape means hunting every copy. That hunt, not the branch itself, is the cost polymorphism removes.
The session stance is therefore conditional: not "never use if," but "do not use conditionals for predicted variation by type." New variations should be created from outside, by adding a new class that implements the same interface. Inheritance (or interface implementation) together with polymorphism lets a team do exactly that.
11.4.4 Mechanisms — Inheritance, Dynamic Binding, Overloading and Overriding
How the language delivers the design — four tools and when each matters. Polymorphism in Java, C#, and similar languages builds on inheritance, but the design idea is independent of the syntax.
- Interface or abstract class declares the polymorphic operation. An interface binds no hierarchy — any class from any branch can implement it. An abstract class ties implementers to its tree. That freedom is the main advantage of interfaces noted in the session: in single-inheritance languages, using an interface leaves the evolution point open to any future vendor class.
- Method overriding lets a subtype replace the behavior of a supertype at runtime. The same name (getTaxes, findArea, landedOn) means similar services; each subtype supplies its algorithm.
- Method overloading gives the same name with different parameter types (for example, polymorphic sqrt for float versus double). Conceptually related, but the GRASP rule focuses on overriding / runtime dispatch, not overloading.
- Dynamic binding means a call through a supertype or interface variable finds the implementation that matches the actual subtype object received at runtime. Type checking should not be done by the caller with branches; it should happen through this dispatch.
The formal inheritance relation used in the lecture:
read as " is a subtype of " — a variable of type can hold an instance of , and a call where actually runs the operation defined in the concrete that was passed. This single line is the wiring that makes the plug swappable. The lecture notes students already studied this as syntax, but its use as a design assignment tool — organizing where responsibilities live — is where many teams avoid it and miss the benefit.
The session adds a concrete language detail: declaring a polymorphic operation \{abstract\} in the superclass when there is no default behavior forces each concrete subtype to provide its own implementation, which is the compiler's way of enforcing the design contract.
Visual intuition: picture a sequence diagram where Sale sends taxes = calculator.getTaxes(sale) to a lifeline labeled calculator: ITaxCalculator. At runtime, that lifeline is bound to TaxMasterAdapter in one deployment and to GoodAsGoldAdapter in another; the arrow is the same, only the object behind the lifeline changes. The takeaway: one arrow in the diagram, many possible bindings behind it.
11.4.5 Worked Examples — Tax Calculators, Shapes and Adapters
This subsection is the evidence the manifest requires: every example below is traced step by step with numbers and with the runtime hand-off made explicit.
Example 1 — Income tax and product tax calculators (next-generation POS). Problem: the terminal must calculate many taxes — income tax, product-specific tax, yearly rule changes — and multiple third-party calculators may exist, each with a different native API (raw TCP socket, SOAP, Java RMI).
Polymorphic design: Define one interface for tax calculation:
Concrete adapters implement it: TaxCalculator2020, TaxCalculator2021, ProductTaxCalculator, ThirdPartyAdapterForVendorA, ThirdPartyAdapterForVendorB. The client (Sale) holds a field calculator: ITaxCalculatorAdapter and asks:
without knowing which concrete adapter is behind it. At runtime, the concrete object that was configured (by factory, dependency injection, or startup config) is used.
Yearly evolution trace: Suppose Sale total is 130 (two items at 50×2 plus one at 30). In 2020, rate 5% applies; in 2021, 5% plus 2% surcharge. The two adapters compute:
- 2020:
- 2021:
Adding 2021 required adding class TaxCalculator2021 implementing getTaxes with 0.07 — Sale was not edited, not recompiled in the binary sense the lecture stresses. The travel power adapter analogy is preserved: one device (Sale) works in multiple countries because only the plug adapter changes while the device's socket stays constant.
Result bolded: New yearly rule = new class, zero edits to Sale, one stable interface call.
Example 2 — Shapes and area calculation (the classic design-to-code bridge). Define super type Shape with polymorphic operation findArea. Concrete subtypes:
- Rectangle(width , height )
- Square(side )
- Triangle(base , height ), Pyramid, Hexagon, Trapezium, etc.
Each provides its own implementation under the same name. When a method takes a Shape parameter:
the runtime type of shape (Rectangle at one call, Triangle at the next) decides which body runs — this is method overriding at work, and the Open-Close consequence will be examined later.
Formulas inside the concrete implementations, worked with numbers:
For , : .
For : .
For , : .
Polymorphic call table with the same client line print(shape.findArea()):
- shape holds Rectangle(4,5) → prints 20
- shape holds Square(4) → prints 16
- shape holds Triangle(6,4) → prints 12
The client line never branched on type; the object did.
Inheritance relation preserved: , , . The variable of type Shape can hold any of them, and the call disperses correctly.
Sense-check: dimensions ✓ (area in ), boundary ✓ (zero side → zero area), special case ✓ (a Rectangle with is a Square geometrically, but as a design famously breaks Liskov — flagged now, resolved in protected variations).
Example 3 — Multiple sorting algorithms and square root variants (why one name, many bodies, is not trivial). The same principle appears where there is no class hierarchy story: polymorphic sqrt with variants for float and double — same conceptual operation, different parameter types, one conceptual name — and Strategy families where sorting chooses an algorithm by data type or size. Each shows the design moral: one interface can expose many implementations and the client codes to the name, not to the variant list. The session cites these to prevent the misconception that polymorphism is "only for shapes."
11.4.6 Interfaces Versus Abstract Classes — Hierarchy Freedom
Scope — when to pick which base. Interfaces do not bind implementers to one hierarchy — any class from any branch can implement the interface. Abstract classes are tied to their own inheritance tree, so implementers must sit inside that family. In Java/C#-style single-inheritance languages, a class can implement several interfaces but extend only one class, so interfaces leave more future room.
Rule of thumb from companion docs (T1 Chapter 25): if you already have an abstract superclass , consider extracting an interface for its public signatures and making implement . Even if no immediate second hierarchy needs it, you have left a flexible evolution point for unknown future cases. Use an abstract class when you need shared state or shared implementation that truly belongs in the base; otherwise prefer the interface to keep the variation point wider.
11.4.7 When Not to Use Polymorphism — Avoiding Futuristic Over-Engineering
Pitfalls — speculative future-proofing. Polymorphism's disadvantage is overuse for predicted variations that will never arrive. Designing a full interface hierarchy "in case something changes," when no change is planned or likely, adds indirection, more classes, and complexity for zero gain. The lecture's exact caution is that apart from that case polymorphism is strongly beneficial — new variations plug in without editing clients — but the futuristic case must be consciously avoided.
Two diagnostic questions the session implies: (1) is there an immediate or very probable variability at this point (yearly tax rules, multiple vendor adapters — yes; a color enum with three fixed values forever — no)? (2) can you name the next concrete variant that would be added? If you cannot name it, the variation point is speculative and the conditional may be the honest design. Overuse also hurts performance — each polymorphic dispatch through a chain of intermediaries adds cost — so the session ties restraint to the Caution in 11.2.5 about not overdoing decoupling.
11.4.8 Student Questions and Answers
Q: What is a pluggable component? How does polymorphism support pluggable components? A: A pluggable component is like an electrical plug. The socket — the interface — is the same for every device. Behind it, a fan or a bulb does different work, but the client does not change — the client sees one stable interface and the server side allows new implementations to plug in behind it. Polymorphism gives software that same quality: one stable interface such as a tax calculator with getTaxes, and many concrete implementations behind it. Clients code to the interface, and new implementations plug in without changing client code. The USB port is the same story for digital devices: one port, many pluggables, one contract.
Q: Why should we avoid if-then-else and switch for alternatives that vary by type? A: Conditionals bake the set of choices into one place. Every new alternative forces a change inside that conditional — and inside every copy of that conditional scattered through the codebase. With polymorphism, a new alternative is a new class outside the existing code. Inheritance together with runtime dispatch keeps the client independent of switches and keeps changes outside the client. The measurable consequence is edit count: one new class versus N edited branch sites. When the family is open-ended (yearly taxes), the N grows each release; polymorphism keeps it at one.
Q: What is the advantage of interfaces over abstract classes for polymorphism? Are there disadvantages? A: Interfaces are not tied to one hierarchy; any class from any branch can implement an interface, while an abstract class restricts implementers to its own tree. That freedom — plus the ability to implement multiple interfaces under single inheritance — is the main advantage. The main disadvantage to watch for is creating interface hierarchies for variations that will never happen — futuristic over-engineering that adds complexity without benefit. Choose the base by the expected shape of future change, not by habit.
Q: Can you clarify the teaching participation prompt about polymorphism being important yet often avoided? A: The feature itself is small — one name, many implementations, runtime dispatch — but its design use is not. Many developers learn inheritance and overriding as syntax for exams and then avoid them in architecture, defaulting to conditionals they could write faster on day one. The session asks teams to study polymorphism as a design assignment tool: plant one interface such as ITaxCalculator, put concrete adapters such as different yearly calculators behind it, and let dynamic binding do the type test. That shift — from "can I write a polymorphic call?" to "where should the polymorphic responsibility live?" — is the design skill being rehearsed.
Real-world note: provider adapters for tax, payment, or mapping services are a direct sales-point example of one interface with multiple vendor implementations selected at runtime by configuration — the same deployment may even swap adapters by feature flag without a code change.
Recap and bridge. Polymorphism handles type-based variation by assigning a shared operation to the variant types and letting runtime dispatch select the body, so new variants are added as new classes outside the client. Conditionals do the opposite — they bake choices inside and force edits. The bridge to indirection is that polymorphism often arrives carried by an indirection layer: the adapter that hides an external API is simultaneously the polymorphic class and the mediating layer. When the coupling problem is not variation by type but simply too many direct lines, the next pattern — indirection — solves it directly.
Exam note: study polymorphism with at least one full example such as tax calculators behind a common getTaxes operation or shapes behind a common findArea operation, be ready to trace the runtime dispatch with actual numbers (20, 16, 12 as above), and be ready to explain why conditional branches would be the weaker design there and why an interface is the freer base than an abstract class.
11.5 Indirection — Decoupling Through an Intermediate Layer
Indirection handles the companion problem to polymorphism: two elements are too tightly coupled to be left talking directly, even when variation by type is not the main issue. Instead of a branching choice, the problem is a thick bundle of direct lines — and the fix is a thin, stable mediator.
Hook — what if the problem is not a choice but a tangle? Suppose Sale talks directly to a relational database through five JDBC calls, a printer talks directly to product storage, and Register talks directly to all of them. Change the database vendor and six classes must be touched. What if every direct line could pass through one narrow doorway instead of six wide doors? That doorway is indirection.
Intuition and analogy — the switchboard operator. Picture an indirection layer (a mediator that sits between two other elements so they no longer depend on each other directly) like an old telephone switchboard operator. Without the operator, every phone in town needs a wire to every other phone — wires. With the operator, each phone has one wire to the switchboard; the operator connects callers on demand. The number of wires collapses, and replacing one phone requires rewiring only its one line.
Mapping: phones = original tightly coupled classes and ; operator = the mediator (for example, PersistentStorage or PricingService); direct wire = direct method dependency; routed call = mediated message. Where the analogy breaks: an operator can become a bottleneck or a God object if every call is forced through one place — that is the performance and cohesion cost indirection must earn.
11.5.1 Definition and Purpose
Formal idea — indirection as a decoupling assignment rule. Indirection means inserting a new, intermediate element between two other elements so that they no longer depend on each other directly. The new element mediates the interaction, and each original side now depends on the smaller, stable mediator rather than on the full detail of the other side.
The session's wording is kept verbatim: whenever two components are highly coupled, layering or an intermediate object can carry part of the interaction. The purpose is to reduce coupling and to protect reuse: the mediator's interface is narrower and more stable than the concrete details it hides, so changes behind it ripple less.
In GRASP terms, indirection is the answer to the placement question "where else could this responsibility live so that two things stop knowing too much about each other?" The answer is often "in a new third thing between them."
11.5.2 How Indirection Reduces Coupling — Layering
Mechanism and a counted example — from direct edges to mediated edges. Picture two components that call each other through many direct links. Inserting a layer splits those links so half the interaction goes through one side of the intermediary and half through the other.
A concrete counted trace from the lecture, cleaned, makes the reduction measurable. Let class depend directly on five methods of class :
Before indirection: carries 5 distinct method dependencies. Fan-out of toward is 5; a change in any of those five signatures touches .
After indirection via :
where knows only the mediator's interface (perhaps one method like sale.save() or pricing.getPrice(product)) and knows only how it is called by the mediator. Direct edges between and fall from 5 to 0; 's fan-out toward the data layer falls from 5 to 1; is now reachable only through . The total number of edges in the system may not shrink, but the graph is redrawn so no client is directly exposed to the other side's internal churn.
The formula is:
The session also notes the layered-architecture system-level use: instead of an upper UI layer talking straight to lower data layers through many calls, a middle service or application controller carries the traffic and narrows the surface between layers. The same indirection principle applies at one-class and at one-layer scale.
Visual intuition: draw two boxes and with five parallel arrows between them. Draw a third box between them and reroute the arrows so two go and three go , each now labeled with a single interface name. The picture changes from a thick cable to two thin, labeled channels — the labels are what the compiler can check and what future change must respect.
Real-world layering: adding a service facade or an application controller between a user interface and a domain model is the standard use of indirection in layered systems — the UI no longer knows whether the domain uses JDBC or JPA underneath.
Assumptions and scope — when indirection helps and when it harms. Indirection helps when two elements are already highly coupled — many direct dependencies that repeat change impact. It helps when the mediator can offer a genuinely narrower and more stable interface than the pair it decouples. It harms when the coupling is already low (adding a layer increases depth and indirection cost without benefit), when the mediator itself becomes incohesive (a God mediator knowing everything), or when path length hurts performance — each hop adds call cost, and traversing long chains is flagged as a performance anti-consequence of high coupling earlier in the lecture (11.2.5) and again for long Demeter chains (11.7.3).
The session's caution about performance is kept: long chains of object connections add traversal cost, so an indirection must earn its keep by measurably thinning the graph.
11.5.3 Relationship to Pure Fabrication
Indirection is often a pure fabrication — the overlap made explicit. An indirection class (the mediator) is frequently a pure fabrication — an invented class that does not model a domain object and has no real-world counterpart named by the domain. It exists purely to carry coordination, reduce dependence, or group shared behavior for reuse.
The session notes this overlap explicitly, and companion text T1 Chapter 25 confirms: the TaxCalculatorAdapter (polymorphic indirection) and PersistentStorage (persistence indirection) are both presented as fabrications that happen to mediate. The design instinct is therefore two steps: first ask "should these two stop talking directly?" (indirection); then ask "does the mediator correspond to a domain thing or must I invent one?" — often the answer is invention. The implication is that you should not search the domain model for a mediator's name; you supply a service name (Adapter, Storage, Service, Controller) that describes behavior, not representation.
Pitfall to avoid: rejecting a good mediator because "there is no such object in the real store." The POS domain has no PersistentStorage entity, but the software needs one for cohesion and reuse. Representational purity is not the scoreboard — coupling and cohesion are.
11.5.4 Student Questions and Answers
Q: When should we add a layer instead of letting two classes talk directly? A: When direct talk creates tight coupling that will amplify change and that coupling spreads to many places. If two objects are tightly coupled and that coupling is about to be repeated — five direct method dependencies today, ten tomorrow when a new vendor appears — a new layer or intermediate object can break the direct line. Splitting dependence through a stable mediator reduces the number of direct dependencies each side must carry and narrows the interface each side must track. The decision rule to quote in an exam is the session's own phrasing: whenever two components are highly coupled, consider layering or an intermediate object; if coupling is already low and performance or scope does not justify it, do not add the layer.
Recap and bridge. Indirection inserts a narrow, stable mediator between two tightly coupled elements so direct edges disappear and each side depends only on the mediator's thin contract. It is the structural fix for tangled wiring, often realized as a pure fabrication class (Adapter, Storage, Facade) and often combined with polymorphism when the mediator must hide varying implementations. The bridge forward is that indirection's most common concrete reason to exist — to group invented, highly cohesive behavior — gets its own pattern name next: pure fabrication.
Exam note: be ready to name the condition for adding indirection (highly coupled elements with many direct dependencies), the mechanism (intermediate layer or object mediating to a narrow stable interface, ), and the overlap statement that indirection is often a pure fabrication.
11.6 Pure Fabrication — Invented Classes That Do Not Exist in the Domain
Pure fabrication is invention with a purpose. It is the pattern you reach for when faithfully following the domain would make a good class bad — too large, too coupled, or duplicated everywhere. It solves a tension that domain classes alone cannot.
Hook — what if the "right" domain class is the wrong software class? Domain thinking says a Sale should save itself — it holds the data, so it is the expert. But give every domain class (Sale, ProductDescription, Customer) its own JDBC save, update, and delete, and you have three copies of the same brittle database code, three reasons to break, and zero reuse. What if the cleanest home for persistence is a class the store has never heard of? That invention is pure fabrication.
Intuition and analogy — kitchen stations versus ingredients. Picture a domain class (a software class that mirrors a real-world thing such as Sale or TableOfContents representation) as an ingredient in a kitchen: tomatoes, basil, mozzarella. Representational decomposition (splitting by what things are) lays ingredients out on the counter. Behavioral decomposition (splitting by what should happen together) builds stations: a grill station, a sauce station, a plating station. A pure fabrication (a class invented purely to group cohesive behavior, with no domain counterpart) is the plating station — it does not correspond to any ingredient, but it groups one coherent behavior (plating) that would otherwise be scattered across every ingredient's station. You would not ask "which ingredient is the plating ingredient?" — you invent the station because behavior, not representation, is the right cut.
Mapping: ingredients = Sale, Payment, ProductDescription; stations = PersistentStorage, TableOfContentsGenerator, FileManager; grouping rule = highly cohesive, loosely coupled behavior; reuse = stations serve many ingredient sets. Where the analogy breaks: in a kitchen, stations are physical; in software, a fabrication is a responsibility home — it can still be misused as a dumping ground if cohesion is not enforced.
11.6.1 What Fabrication Means — Imagination Versus Domain Classes
Formal idea — pure fabrication defined against representational purity. Pure fabrication is a class that is purely invented by the designer. It has no counterpart in the problem domain — it does not stand for a person, thing, or event the client described — and it would not appear in the domain model that mirrors the real world. It is a convenience class created to group a highly cohesive set of responsibilities that would otherwise have no good home, or would make a domain class incohesive.
The name surprises beginners because early analysis teaches fidelity to the domain — low representational gap — and fabrication feels like cheating. The lecture reframes fidelity: most early classes do come from the domain through the Expert pattern, but when those domain classes become the wrong place for a responsibility because they would grow too large or too coupled, fabrication gives a clean alternative. Separation of concerns outranks representational literalism at that point. The fabricated name is usually behavioral — PersistentStorage, Generator, Manager, Handler — not a domain noun.
Real-world family: logging, security, caching, persistence helpers, and export generators are common fabricated classes. None of them names a thing on the shop floor; all of them group behavior that crosses many domain things.
11.6.2 When to Invent a Class — Cohesion or Coupling Problems That Expert Cannot Solve
Decision rule — Expert first, fabrication when Expert hurts the scoreboard. Consider pure fabrication when keeping a responsibility with the information expert would violate high cohesion or low coupling, and no good domain holder exists.
More concretely, the lecture gives a test: if domain-layer classes already accumulate high coupling or low cohesion, and Expert would place more work there, pause and invent a highly cohesive, loosely coupled alternative that supports reuse. The new object carries the related bundle of tasks whole, keeps each domain class focused, and reduces the spread of dependencies. The session's summary phrase is that pure fabrication supports high cohesion, low coupling, and high reuse as the shared aim.
This is a judgment call, not a formula. Not every helper deserves a class, and fabrication should not be used where Expert already gives a clean placement — a Sale knowing its own total is a good Expert placement with no cohesion cost. Fabrication is the remedy for the specific failure where Expert would make a domain class do too much: persisting itself through JDBC, generating its own paginated table of contents while also representing the contents, or handling files for every domain type identically.
Scope — when invention is warranted and when it is waste. Fabricate when you can name a single, testable cohesion theme ("this class does persistence and only persistence") and when callers will depend on a narrower interface than they would if the responsibility stayed with many domain classes. Do not fabricate to wrap a single trivial call that already has a narrow interface, or to create a manager that merely forwards. The lecture stresses restraint: fabricated helpers should be fine-grained and convenience-sized, and overuse is its own smell — the next section warns that behavioral decomposition into pure fabrications is sometimes overused by newcomers who are more comfortable decomposing by functions. The signal is literal: if most data of a fabrication is just passed-in arguments from callers with no shared state, the behavior may still belong with the data holders.
Visual intuition: draw two sketches. Sketch A (Expert-only persistence): Sale, ProductDescription, and Customer each have Save/Update/Delete boxes hanging off them — three repeated boxes, three database driver lines. Sketch B (fabricated): one PersistentStorage box with Save/Update/Delete inside it, and three thin arrows from each domain class to that one box. Takeaway in one sentence: fabrication collapses duplicated behavior into one cohesive, reusable home and replaces many thick database edges with one thin storage interface.
11.6.3 Behavioral Decomposition Versus Representational Decomposition
Two cuts — what things are versus what should happen together. The session draws a distinction that explains why fabrication feels different even before you name it.
- Representational decomposition (also called representational decomposition, after Peter Coad/UML domain modeling) mirrors objects in the problem world: Sale, ProductDescription, Payment, TableOfContents. It supports low representational gap and is the source of most early classes via Expert.
- Behavioral decomposition (also called functional decomposition) groups by what should happen together, without concern for representing a domain thing: storing data persistently, generating a formatted report, caching remote results.
The fabricated class is organized around behavior — it is partitioned by related functionality, so it is a function-centric or behavioral object — rather than around a domain noun. Because it follows behavior, pure fabrication can appear as a service or handler that has no domain twin yet is the clean home for a set of related methods that would otherwise be scattered or duplicated.
Text T1 Chapter 25 makes the same cut with the example pair TableOfContents (representation — holds chapter names, page numbers, consistent with our concept of a real table of contents) versus TableOfContentsGenerator (behavior — produces that value from a document structure). One is representation; the other is pure behavior. Both are valid classes, but they come from different cuts.
The textbook gloss also notes: many GoF patterns (Adapter, Strategy, Command) are pure fabrications by this test — they group behavior for reuse, not representation. Identifying a class as a fabrication is not the point; understanding which cut you used is.
11.6.4 Worked Examples — Persistent Storage and Table-of-Contents Generation
Each example below is traced with the three-way scoring the manifest requires: cohesion, coupling, and reuse plus duplicate-code elimination.
Example 1 — Persistent storage for sales (the canonical GRASP fabrication). Requirement: every Sale must be saved, updated, or deleted in a relational database.
Expert-only design (before): Sale holds its own data, so Sale.save() implements JDBC: open connection, build SQL insert, handle exceptions, close connection. Repeat identically in ProductDescription.save(), Customer.save(), and every other domain class that persists. Degree of coupling: each domain class now depends on JDBC, SQL dialect, connection pooling — fan-out rises by 3 per class. Cohesion: Sale now has two unrelated themes — sales logic and database access — so cohesion drops from "one purpose" to "two." Reuse: none — saving logic is copy-pasted. Change cost: switching databases touches every domain class.
Fabricated design (after): Create one new class whose whole job is persistence:
Each domain object delegates: storage.insert(thisSale) rather than thisSale.saveItself(). PersistentStorage internally holds the database connection and SQL mapping.
Scoring: Cohesion — Sale stays focused on sale logic (high); PersistentStorage groups one related behavior (high). Coupling — domain classes depend on one narrow interface PersistentStorage instead of JDBC/SQL details (lower and stable). Reuse — one generic persistence class serves many domain types (high). Duplicate code — three copies collapse to one (eliminated). The session explicitly notes the tension: Sale is the information expert for saving that particular Sale, but keeping the full persistence logic there would make Sale too large and incohesive, so the behavioral responsibility is fabricated elsewhere. In a real framework, this front-end fabrication leans on many back-end helpers — the session flags that detail so you do not imagine one God class replacing many.
Result bolded: persistence moves out of domain classes into one fabricated handler, coupling narrows to one stable interface, and duplicate JDBC code is eliminated.
Example 2 — Table of Contents generation (representation versus generation). Domain model: a TableOfContents object represents the contents themselves (chapter names, page numbers). Generation requires traversing the document tree, paginating, formatting, and collecting entries.
Before (no fabrication): tableOfContents.generate() does both jobs — holding the result and computing it — which makes the same object both data holder and algorithm host. Cohesion: two themes (representation + generation) → low.
After (fabricated): TableOfContentsGenerator takes a Document structure as parameter and returns a TableOfContents value:
Generator has no domain counterpart as a thing the client described; it is a fabricated handler whose single purpose is generation. By separating representation from generation, both classes stay cohesive and each is reusable alone — the representation in view code, the generator in batch export code. The chapter text T1 Chapter 2 and 25 both cite a generator object as a textbook behavioral cut that students should recognize as a legitimate class even though no chapter entity called "Generator" walks the real world.
Result bolded: representation and generation split into two cohesive classes, each reusable without the other.
Example 3 — Grouping common infrastructure behavior (FileManager, ConnectionManager). In a larger system where several domain objects need file or network handling through the same low-level details, a fabricated FileManager or ConnectionManager groups that common behavior once. Callers depend on the fabricated helper's narrow interface, not on duplicated low-level details in each caller. This is the closure of examples 1 and 2 at infrastructure level: one cohesive helper, many callers, narrow coupling, concrete reuse.
In each case, the session stresses restraint: these are fine-grained, convenience helpers. They should not become a God fabrication that hoards unrelated helpers (persistence + reporting + mail) — that would recreate the same low-cohesion problem one level up.
11.6.5 Student Questions and Answers
Q: If Expert says to put work where the data lives, why invent a class that holds no domain data? A: Because Expert can push a domain class past its cohesion or coupling limit. Expert optimizes for information locality, but locality can overload. When the information expert would become incohesive or highly coupled — for example, Sale holding all JDBC persistence logic plus line-item math plus discount rules — the designer trades locality for focus: a fabricated class takes the related but separable behavior as its whole job. That fabricated class supports high cohesion and low coupling where Expert alone would not. Expert remains the default; fabrication is the specific remedy when Expert would make a domain class do too much. This is also why different courses sometimes phrase the test as "does keeping this responsibility with the expert force this class to know about a subsystem (database, file system) it should not know about?"
Q: Are polymorphic behavioral classes also pure fabrications? A: Often yes. A family of tax calculator adapters or content generators may have no domain counterpart as named by the client — no one in the store says "I work with a TaxMasterAdapter." They are invented to isolate behavior and to let new variations plug in behind one stable interface. Whether they count as pure fabrications depends on context you are asked about, but the reasoning is the same: group cohesive behavior for reuse and for stable interfaces. The exam expects you to note the overlap: a polymorphic adapter family is both a polymorphism story and a fabrication story, and either label is defensible if you justify it.
Q: Does pure fabrication replace Expert? A: No. Most objects still come from the domain through Expert and low representational gap — that is the healthy default that keeps domain logic with its data. Fabrication is the alternative for the subset of responsibilities where Expert would create hindrance in coupling or cohesion: persistence, cross-cutting generation, adapter families for external APIs. Think of fabrication as the pressure valve, not the pipeline.
Recap and bridge. Pure fabrication invents a new, behavior-centered class purely to give highly cohesive, loosely coupled behavior a good home — typically persistence, generation, adapters, and infrastructure groups — and it is the right call when keeping the responsibility with the domain expert would make that expert incohesive, highly coupled, or would duplicate one algorithm across many classes. The bridge forward is that fabrication plus indirection together set up the umbrella that protects the resulting structure over time: protected variations. The next section examines that umbrella and its two concrete checks — Liskov substitution and the Law of Demeter.
Exam note: be ready to justify when a persistence or generation responsibility should leave its domain class and move to a fabricated helper, naming the change in coupling, cohesion, reuse, and duplicate code, and to sketch the before-and-after wiring that collapses many database edges into one stable storage interface.
11.7 Protected Variations — Stability Through Interfaces, Liskov Substitution and Law of Demeter
Protected variations is the most general pattern in this group and the one that ties the lecture's vocabulary together. It asks the designer to identify points of predicted variation or instability and to protect those points with stable interfaces behind which variation can happen — so clients who depend on the interface are not forced to change when the varying element changes.
Hook — why protect a point before it hurts? You already know how to fix coupling today with polymorphism and indirection. But what about next year's new tax vendor, next quarter's new payment device, or next sprint's refactored database mapping? If every change forces you to reopen every client that touches the varying decision, you have not fixed the future — only today's graph. Protected variations is the practice of wrapping tomorrow's likely changes today.
Intuition and analogy — the firewall and the contract. Think of protected variations (isolating a variation point behind a stable interface so extension does not require modification) as a firewall with a single, well-documented gate. On the far side of the firewall is the unstable, fire-prone area — the varying tax APIs, the shifting object graph, the private state that tempts direct access. The firewall is the stable interface (ITaxCalculator, Shape, private field plus accessor). Clients on the safe side know only the gate's address and contract. The fire can be rebuilt behind the wall without evacuating the town, as long as the gate's contract holds.
Two sub-forces inside that firewall have their own sharp analogies:
- Liskov Substitution (subtypes must honor the supertype contract so substitution is silent) is like a plug-type standard: any bulb that says "Edison screw E27, 230V" must work in any E27 socket — you should not need to read the factory that made it. If a subtype bulb draws double current despite claiming the standard, the standard is broken.
- Law of Demeter (don't talk to strangers through long chains) is like a chain of command: a soldier talks to their direct officer, not to the officer's officer's officer. The longer the chain a message hops, the more fragile the order.
Where the analogies break: a real firewall blocks fire completely; a software interface blocks only the change it was designed to hide. Protecting the wrong decision or the wrong interface leaves the other side exposed.
11.7.1 Definition — Protecting Points of Predicted Variation
Formal idea — PV as the umbrella, with a stable interface as the mechanism. Protected variations (PV) is the principle that variation in behavior, data, software, hardware, operating system, or external subsystems should be isolated behind a stable interface, so that clients are unaffected when the variation element changes. The protected element can be extended — a new variation can be added — without modifying the code that uses it; the interface remains steady while implementations behind it change, and flexibility is provided so that changes in one area do not force changes elsewhere.
The session places PV as the umbrella statement. Other ideas — information hiding (hide design decisions), encapsulation (private state plus intentional operations), interfaces, polymorphism, indirection, the Open-Close principle, Liskov substitution, and the Law of Demeter — are concrete ways to achieve protected variations for different kinds of variation (type variation, structural variation, state exposure). The wording kept for reconciliation is that variations should be made safe for extension by means of interfaces and related hiding mechanisms, and flexibility should be provided so that changes in one area do not force changes elsewhere.
Critically, PV is not "protect everything." It protects predicted or foreseen variation — change you can name — not every imaginable change. The lecture returns to this scope point in 11.7.3 and again in 11.8.6-11.8.7; over-protection is itself a coupling and performance cost.
Visual intuition: draw a vertical wall labeled ITaxCalculator. On the left, one client box Sale has a single arrow labeled getTaxes into the wall. On the right, behind the wall, three boxes (TaxMasterAdapter, GoodAsGoldAdapter, FutureVendorAdapter) each plug into the same wall socket. The takeaway: one wall, one contract, any number of implementations behind it — new ones are added on the right, nothing on the left moves.
11.7.2 Liskov Substitution Principle — Subtypes Must Stand In for Supertypes
Formal statement — from the lecture verbatim, cleaned and aligned with companion text T1 Chapter 25 and T4 Chapter 10. The Liskov Substitution Principle (LSP), formulated by Barbara Liskov in 1988, is the classic statement of protected variation for type hierarchies: software written to use a supertype should work correctly with any subtype of , without needing to know which subtype was actually supplied.
The session's formal property, preserved exactly and typeset as in the companion texts:
For each object of type there is an object of type such that for all programs defined in terms of , the behavior of is unchanged when is substituted for , where is a subtype of .
In symbols used for fast recall in the lecture:
read as: if is a subtype of , then for every program written in terms of , the behavior of using a is the same as the behavior of with an substituted for that . The verbal gloss alongside the formula is kept as: wherever a supertype or an interface type is expected, passing a subtype object must also work and must preserve correct behavior, including preconditions, postconditions, and invariants — not just type compatibility.
An immediate consequence is dynamic dispatch: method overriding and interface implementation work precisely because callers depend on the supertype contract , not on which subtype happens to arrive at runtime. Where the supertype declares an operation, every subtype must honor that contract. Companion text T1 Chapter 25 adds the diagnostic phrasing: LSP formalizes the intuition that a method like addTaxes(ITaxCalculatorAdapter calc, Sale sale) should continue to work as expected no matter what actual ITaxCalculatorAdapter is passed in.
Assumptions and scope — what LSP requires beyond "it compiles." LSP is not satisfied by subtype syntax alone. A subtype that compiles but widens preconditions (demands more), weakens postconditions (promises less), throws new checked exceptions, or mutates inherited state in a way that breaks invariants violates substitution even though the type checker is silent. The classic design clash flagged in this lecture's shape family — — illustrates the scope: a Rectangle offers setWidth(w) and setHeight(h) independently; a Square cannot honor independent mutation without breaking the square invariant (width must equal height). Geometrically in sets, but behaviorally is not a behavioral subtype of mutable . LSP tells you to model immutable shapes separately or to share an abstract Shape without the independent mutators. The same lesson applies to vendor adapters: an adapter that returns taxes in a different currency than the interface promises breaks substitution even if getTaxes compiles.
Visual check: picture the subtype hierarchy as a set diagram and the contract as a shadow. Every subtype's shadow should sit inside its supertype's shadow — smaller preconditions shadow, at least as strong postconditions. Where a subtype's shadow sticks out, substitution will surprise callers.
11.7.3 The Law of Demeter — Don't Talk to Strangers
The structural twin of LSP — a coupling rule about how far a message should reach. The Law of Demeter (also called Don't Talk to Strangers) is another concrete form of protected variations, focused not on type contracts but on structural paths. It says a method should only send messages to a small, well-defined set of objects that are close to it — its familiars — not to distant strangers reached through long chains of intermediaries.
The rule stated for Java and C# style code, preserved verbatim from the lecture and aligned with T1 Chapter 25:
Within a method of object , send messages only to:
- itself
- a parameter of the method (including objects passed in as arguments)
- an object created inside
- an element of a collection that is held as an attribute of
- an attribute object of — the "close connection" phrasing in the session
A method should not traverse long object connections to talk to indirect objects, for example:
where talks to , then to , then calls an operation there. Such a chain creates tight coupling between far-apart objects, makes the design fragile when any intermediate object changes its internal structure, and hurts performance by extending navigation. The lecture's exact consequence language is kept: long chains and long dependencies should be avoided because the resulting design is fragile with respect to changes in any linked object, and keeping connections short also supports data encapsulation since fewer objects expose internal structure.
Companion text T1 Chapter 25 re-states the intent as protection against instability in object structure — don't couple a client to knowledge of indirect connections — and clarifies that the "collection element" case counts as a familiar.
When Demeter matters and when to relax it. The session marks Demeter as especially valuable in young applications and early iterations where object structure is unstable, and less urgent for stable, mature library code where the path sale.getPayment().getTenderedAmount() is unlikely to shift. Companion text adds that blindly obeying Demeter by adding delegating methods sale.getTenderedAmountOfPayment() everywhere can bloat a class with many tiny pass-throughs that merely echo another object's protocol. The balanced move, which the lecture endorses, is to move the responsibility where it lives — for example, sale.getTenderedAmount() as a domain-meaningful method that hides the internal payment representation — rather than auto-generating a delegating accessor per stranger.
A useful mechanical check: count dots in a message chain inside a method body. One dot after this (like this.printer.print(r)) is usually fine; three dots via successive getX().getY().getZ() is a Demeter smell. The fix is not to delete the dots syntactically but to relocate the responsibility that the last dot was reaching for.
11.7.4 How Protected Variations Uses Interfaces, Encapsulation and Private State
The three cooperating mechanisms — interfaces, hiding, polymorphism + indirection — in one story. PV is achieved through a combination of mechanisms that hide the varying part in complementary ways:
- Stable interfaces hide the behavioral variation. ITaxCalculator hides which vendor's algorithm answers getTaxes. Shape hides which findArea body runs.
- Information hiding plus encapsulation hide internal decisions and state from other modules, most directly by making state private and exposing only needed operations. Private state is the simplest PV gate: clients are not even coupled to the existence of a field, only to the operation you choose to expose.
- Polymorphism plus indirection then let new variations plug in behind those stable, encapsulated interfaces without reopening clients.
The session adds a candid practical observation from code review: in instruction, teams are told to use more private variables; in practice, many codebases leave state public so it is widely accessible by mistake or convention, and other modules freely read or write internals that were meant to stay local. Public state increases exposure and coupling because any client can depend on internals that the author considered free to change. Code refactoring passes — scanning for public fields, long Demeter chains, large low-cohesion classes — repeatedly surface this exact gap. Good encapsulation (private state with controlled access) is the lowest-cost protected-variations technique and, paradoxically, the part teams skip most often.
A small before-and-after for encapsulation makes the cost visible:
- Before (exposed):
sale.payment.tenderedAmount = 150— every client directly knows Sale has a public Payment field with a public amount. Rename the field or change the representation and every direct writer breaks. - After (encapsulated):
sale.makePayment(150)— Sale alone knows how a payment is represented. The assignment behind it may change from a field to a value object or event log without any client edits.
11.7.5 Worked Examples — Shapes, Cars and Adapters at Runtime
Each example below is the manifest-required worked case for 11.7, traced to show the runtime substitution or chain correction explicitly.
Example 1 — Shapes (LSP in its everyday form). Declare supertype Shape with a parameter of that type in a method:
The method must work correctly when the actual argument at call sites is any of Triangle, Rectangle, Square, Pyramid, Hexagon, or Trapezium. Each subtype provides its own findArea/draw body behind the same name, and the call through the supertype selects the subtype body at runtime (dynamic dispatch). Concretely:
- Call
render(new Rectangle(4,5))→findAreayields 20. - Call
render(new Square(4))→findAreayields 16. - Call
render(new Triangle(6,4))→findAreayields 12.
In every case, the render body is identical — it knows only the Shape contract. Wherever a supertype Shape is expected, any specialized shape can be substituted, with no branching in render. This is LSP's phrase made operational: one (= render), many behind it, same behavior shape.
Sense-check: if any subtype required a different render protocol (for example, Triangle.findArea required an extra argument), substitution would break — that variant would not be an LSP subtype for this .
Example 2 — Cars (the analogy the lecture reuses in 11.7 and 11.4). A method price(Car c) operates on a Car supertype. Concrete types — sedan, hatchback, sports variant — each subtype implements the pricing or reporting hook differently but must honor the Car contract (for example, price never negative, invariants about seating and engine coupling preserved). At the call site, the supertype variable Car c is replaced by the concrete car that was passed, with no change to the method body. New car types are added as new subtypes without editing price. This is LSP at the conceptual level the lecture uses to bridge shapes to any hierarchy.
Example 3 — Income tax adapter at runtime (the enterprise substitution story). A program that calculates income tax is defined in terms of an interface type:
Concrete runtime objects may be and (the lecture's vendor-flavored names) or, in the T1 case-study wording, TaxMasterAdapter and GoodAsGoldAdapter. Each implements:
A client method:
At deployment A, calc is bound to ; at deployment B, to . No matter which subtype is passed where is expected, the behavior of the surrounding program stays correct, because the type hierarchy was designed to satisfy LSP and polymorphism delivers the actual body.
Numbers to anchor the substitution: Sale total 200. Adapter applies Vendor A rules (say 6% flat): tax 12. Adapter applies Vendor B rules (6% plus 2% local): tax 16. Same call line, different plugged object, both answers honor the same contract (returned list sums to correct total, no side effects on Sale).
Result bolded: same caller line, different vendor behind the same interface, LSP guarantees the caller's behavior is unchanged by the swap.
Example 4 — Demeter chain violation versus correct close connections (the fragile chain made visible). Compare two message sequences inside Sale-related behavior:
Good — one dot to a close familiar:
where printer is an attribute of this. Dots = 1, familiar = attribute. Fine.
Fragile — three dots through strangers:
where the method chains through customer to order to line item to reach print. Dots = 3, strangers = Payment/Account interiors in earlier encoding. The caller is now coupled to the internal structure of three intermediate objects. A change in how Customer holds Orders — for example, orders becomes a map keyed by date instead of a list — breaks the caller even though the caller only wanted to print a line item. The Demeter fix is behavioral, not syntactic: give the intermediate objects the responsibility being reached for.
instead of reaching into Sale→Payment→Account→Holder. The session's closing fix phrase is: send messages only to close connections — this, parameters, objects the method creates itself, and elements of owned collections — and give the intermediate objects the responsibility that was being reached for.
Result bolded: long chains mark fragile coupling; the repair moves responsibility inward so messages stay local.
11.7.6 Student Questions and Answers
Q: What does Liskov substitution add beyond polymorphism? A: Polymorphism is the mechanism — same name, many implementations, runtime dispatch. Liskov substitution is the correctness condition that makes that mechanism safe. It states that any subtype implementation must honor the contract of the supertype — preconditions, postconditions, invariants — so a program written against the supertype still behaves as expected when a subtype is substituted. Without LSP, inheritance compiles but callers are surprised by a subtype that demands more or promises less. Inheritance and polymorphism work through this principle; LSP is what enforces that they work correctly, not merely syntactically.
Q: Is protected variations just another name for Don't Talk to Strangers? A: This is a common narrowing the session explicitly corrects. Don't Talk to Strangers — the Law of Demeter — was historically described as the original, flagship case of protected variations: protect stability against one specific kind of variation, namely long message chains through shifting object structures. Protected variations later broadened to cover any predicted point of change — behavior, data, hardware, operating system — not only chains. The same protective idea sits behind both — keep clients stable while the varying part changes behind an interface — but PV is the umbrella and Demeter is one important instance under it. Expect exam language to test that scope distinction: PV encompasses Demeter, not the reverse.
Q: When should a Law of Demeter warning be taken seriously? A: When a method navigates through several intermediate objects to reach its real target, creating a long dependency path — exactly the a.getB().getC().doSomething() smell. Such designs become fragile (a change in how B holds C breaks the caller), hard to change (the path must be updated in many places), and slower (extended traversal). The advice is to send messages only to close connections — this, parameters, objects the method creates itself, and elements of owned collections — and to give the intermediate objects the responsibility that is being reached for. A single dot to a stranger (for example, sale.getPayment() in early iterations where Payment is stably housed in Sale) is mild and often deferred; a three-dot chain to a stranger is a serious PV concern and should be refactored now.
Real-world note: most Java and C# style guides now include Demeter as a code-review check, and substitution failures typically surface as broken overrides that change preconditions, throw new exceptions, or alter return semantics — exactly the behavioral violations LSP was written to catch.
Recap and bridge. Protected variations protects foreseen change behind stable interfaces; Liskov substitution is the type-contract check that makes polymorphic substitution safe (subtypes honor the supertype contract ), and the Law of Demeter is the structural check that keeps messages local so structural change does not shatter distant callers. Together they are two concrete gates into the same firewall, and both are exercised through encapsulation: make state private, expose only the behavior the contract promises, and let new variations arrive as new classes behind the same gate. The bridge to the next section is that PV, LSP, Demeter, information hiding, and the Open-Close principle all share one lineage: they are different expressions of protecting variation through stable abstractions, and the lecture now names the axioms from which that whole family derives.
Exam note: be ready to state LSP in words and in the formal form, to spot a violation where subtype behavior widens preconditions or weakens postconditions, and to correct a Demeter chain violation by moving responsibility to a close collaborator rather than by adding syntactic pass-throughs alone.
11.8 Design Axioms, Theorems, Information Hiding and the Open-Close Principle
Under every pattern lies a claim about what makes a design good. This closing section gives that claim a theory vocabulary borrowed from axiomatic design — axioms, theorems, and corollaries — and then applies it to two design axioms, to information hiding and encapsulation, and to the life-cycle statement of the Open-Close principle. The goal is to show that low coupling, high cohesion, indirection, and protected variations are not isolated tips but consequences of a small set of accepted truths.
Hook — from rules of thumb to a theory. You have seen six patterns that all aim at the same thing — fewer surprises when things change. Is that coincidence, or does a single idea generate them all? Axiomatic design suggests the second answer: start from two axioms, derive theorems and corollaries, and the patterns you have been applying become provable consequences rather than folklore. This section names those axioms.
Intuition and analogy — axioms as bedrock, theorems as buildings. Think of an axiom (a fundamental truth with no counter-example) as bedrock — the solid rock you do not argue about. A theorem (a proven consequence that follows from axioms) is a building whose foundations sit on that bedrock. A corollary (a proposition that follows from an axiom or theorem in a straightforward way) is an extension built onto the building. If the bedrock shifts, every building on it sways; if the bedrock holds, every building derived from it holds too. Likewise, theorems in design are valid only when the axioms they rest on are valid — the lecture stresses this conditional validity because it explains why principles that work in one context (product with life cycle) may not apply where the axioms do not hold (throwaway script).
Where the analogy breaks: bedrock is physical; axioms in design are chosen — you adopt independence and information as useful, not as laws of nature.
11.8.1 Axioms as Fundamental Truths, Theorems as Derived Laws, Corollaries as Propositions
Definitions with the design context attached. Precisely:
- An axiom is a fundamental truth that is always observed to be valid, with no counter-example offered in the domain where it is adopted. It is not proved inside the theory; it is assumed at the start.
- A theorem is a proven consequence that follows from axioms and is itself expressed as a law or principle that guides decisions. Because it is derived, a theorem is valid only when the axioms it rests on are valid.
- A corollary is a proposition that follows from an axiom or theorem in a straightforward, often one-step way — a near-immediate specialization.
The verbal description kept from the session is that we want to apply design axioms, principles, corollaries, and guidelines to refine UML class and interaction diagrams, and that theorems are valid only when their supporting axioms are. This is the same structure used in theory courses and more formally in T1 Chapter 17/25's pattern literature: start from a small set of accepted truths, then generate laws that guide responsibility placement and collaboration wiring.
In this lecture the two design axioms below play the role of bedrock, and low coupling, high cohesion, information hiding, protected variations, and the Open-Close principle are the buildings and extensions derived from them. That lineage is what lets a reviewer say "this design violates the independence axiom" and mean something precise.
11.8.2 The Two Design Axioms — Independence and Information
The two bedrock axioms — independence and information — and their GRASP translations. Both axioms are attributed in the session to axiomatic design theory, whose principal author is Nam P. Suh (MIT, 1990 book The Principles of Design; first papers late 1970s). The audio's rendered name Sue matches Nam P. Suh in context, and the axioms below are stated in Suh's canonical phrasing with the lecture's own elaboration preserved.
- Independence Axiom — Maintain the independence of functional requirements (FRs) and minimize interdependence between components that satisfy them. A good design satisfies each functional requirement without affecting other requirements or other components. During implementation, each component should be as independent as possible, with the lowest practical dependence on others. In GRASP language, this axiom is the theoretical parent of low coupling and separation of concerns — it says keep the wiring thin — and it directly motivates the concern for simplicity through lower coupling.
- Information Axiom — Minimize the information content of the design. Equivalently, minimize complexity: among designs that satisfy the independence axiom and meet the same requirements, prefer the one with lower information content — a clearer, simpler mapping from what is needed to what is built. Other things being equal, the design with lower information content (lower structural complexity, fewer assumptions, fewer special cases) is preferred. In GRASP language, this axiom maps to high cohesion and conceptual simplicity — keep each component's content related and the design as a whole compressible to a short description.
The lecture notes the close match explicitly: independence behaves like low coupling formalized as a theory axiom, and minimizing information behaves like high cohesion plus Occam's simplicity formalized the same way. The two together predict the same preference ordering that the yin-yang discussion in 11.2 reached by experience — they are not a different vote, but the theory behind that vote.
A concrete check that shows both axioms at work: Design X spreads sale completion across five tightly coupled helpers (low information per helper but high total wiring); Design Y localizes it in two cohesive helpers behind narrow interfaces (slightly more per helper, far less wiring). Independence penalizes X's web; information penalizes X's scattered description length; Y wins on both.
11.8.3 Occam's Razor — Simplicity as a Design Driver
Occam's Razor — the session's explicit simplicity companion to the information axiom. A related idea named directly in the session is Occam's Razor, the principle of simplicity. It states: when two designs satisfy the same requirements and the same functional dependencies, the simpler one — the one that adds fewer entities, fewer assumptions, or fewer special cases — is preferred.
The lecture raises it as a question to the room and keeps it as the background driver for both axioms, especially where a design adds fabricated or layered elements that must earn their place. Every extra adapter, layer, or handler introduced by indirection, fabrication, or protected variations is tested against this razor: does this indirection narrow coupling enough to pay for its added conceptual weight? If not, the simpler, more coupled sketch may be the honest design for now.
The noisy audio passage that originally carried a verification marker is reconciled here to its textbook sense: the principle of simplicity, not a specific phrasing quirk. The lecture's intended takeaway — when two designs meet the same requirements, pick the one that posits fewer entities — is the stable content to remember, and it directly supports both axioms by asking that independence not be bought with gratuitous machinery.
11.8.4 Information Hiding and Encapsulation — The Private-Variable Habit
Two names for related but distinct ideas — hiding is the decision, encapsulation is the enforcement. Information hiding, introduced by David Parnas (1972, "On the Criteria To Be Used in Decomposing Systems into Modules"), means hiding design decisions from other modules — at points of difficulty or likely change — so that accidental or uninformed changes by clients do not happen. Each module is designed to hide one such decision from the others. It is not a synonym for data encapsulation, though textbooks often collapse the terms. Encapsulation is the language-level practice that enforces hiding, most directly by making state private and exposing only intentional operations.
The session makes a practical, review-derived point that anchors the theory: in instruction, teams are told to use more private variables; in code reviews and refactoring passes, reviewers find the opposite — state left public so it is widely accessible by mistake or convention, and other modules freely read or write internals that were meant to stay local. Public state increases exposure and coupling because any client can read or write internals that the author considered free to change, and it defeats protected variations at its cheapest gate.
The advice is therefore deliberately conservative: hide information by default and treat public exposure as an exception that needs a reason and a documented contract.
Visual: imagine a class box where the top compartment (attributes) is shaded and labeled private. Only the middle compartment (public operations) has arrows leaving the box. The shading is the hiding; the language's private keyword is the lock on the shade. The takeaway: the number of arrows leaving the shaded part should be near zero.
11.8.5 The Open-Close Principle — Closed for Modification, Open for Extension
Life-cycle statement — stability across releases. The Open-Close Principle (OCP), coined by Bertrand Meyer (1988, Object-Oriented Software Construction), is a fundamental rule for stability over a product life cycle. It says:
More precisely, as the session and companion text T4 Chapter 9 rephrase: once clients depend on a stable interface, the team should avoid changing the source or binary of that interface's module in a way that forces clients to change. Instead, new behavior should be added by extension — a new class, a new implementation behind the same interface — so the existing contract stays unchanged. The principle applies at all levels: classes, methods, attributes, relationships, and components, and it is one reason protected variations and polymorphism are valued — they let new needs arrive as new extensions rather than as edits.
The verbal description kept from the session is: once a working client habit is formed around one interface, changes that break that habit should be avoided, and the design should allow new variations through extension while keeping the existing contract unchanged.
For recall, the symbolic relation, shown as a correct display block:
read as: for a new requirement , introduce a new implementation of the stable interface and avoid modifying that depends on . The English alongside the relation is kept: clients should continue to work as before, with new variation supplied through a new implementation of the same interface. Concretely, this is the ITaxCalculatorAdapter extension story: 2021 tax rules arrive as new class TaxCalculator2021 implementing the existing ITaxCalculator; Sale, which depends on ITaxCalculator, is not modified.
The session notes the relationship chain the axioms predict: low coupling supports indirection and fabrication; indirection enables protected variations; encapsulation (hiding) enables the Law of Demeter; following protected variations helps satisfy the Open-Close principle. The chain is not rhetorical — each link is a specialization of the independence or information axiom at a different scale.
Scope — when OCP matters and what "closed" really means. Closed does not mean "never edit the module's private internals." It means closed to modification in ways that affect clients — clients are not forced to recompile or rewrite because the module's public contract changed. Under the hood, the new extension's private code is new; the existing clients' binary remains untouched. The companion text T1 Chapter 25 and T4 Chapter 9 both stress that modules can be open to private extension while closed to client-affecting change, which is why a private field rename inside an encapsulated class is OCP-compliant but a public signature change is not.
11.8.6 How All Principles Connect — Reuse, Maintenance and Performance
The session closes the principle chain by noting that the goals are not isolated and the costs are not free. When the derived principles are respected, the system gains a cluster of good properties: better reuse (small cohesive classes travel), easier maintenance (change is contained behind stable interfaces), and steadier performance (wiring is narrow, chains are short) because the independence axiom is honored. That cluster is why experienced reviewers scan specifically for public fields, long Demeter chains, and large low-cohesion classes — those are the direct signals that one of the axioms is being violated.
When the principles are overused, the opposite cluster appears: too many layers, too many fabricated helpers, or generalizations for variations that never arrive raise indirection depth, hurt performance by lengthening paths, and inflate information content — violating the information axiom. The caution repeated from 11.2.5 and 11.4.7 is therefore grounded in theory: do not chase generalizations that will never be used, because the information axiom penalizes complexity that does not pay for itself. The right question is whether a variation is anticipated, nameable, and worth protecting now; if not, the simpler, slightly more coupled sketch is the axiom-preferred design.
A one-paragraph map of the full derivation the lecture implies: independence + minimal information (axioms) → low coupling + high cohesion (first theorems) → Expert/Creator/Controller as scored placements → polymorphism/indirection/fabrication as repair constructions when first placements increase coupling → protected variations (with information hiding) as the shielding strategy → Liskov and Demeter as type and structure checks inside the shield → Open-Close as the life-cycle consequence (new variation = new extension behind the same stable contract). Failures at any link predict failures at the links above.
Real-world echo: refactoring assistants in many codebases now scan mechanically for exactly these signals — public state (hiding violation), chain length (Demeter), God class size (cohesion), fan-out spikes (coupling) — because those metrics are the operational proxies for the two axioms.
11.8.7 Student Questions and Answers
Q: What is the difference between an axiom, a theorem and a corollary in design? A: An axiom is a fundamental truth with no counter-example in the adopted theory — independence and information are the lecture's two. A theorem is a law that follows from axioms and is valid only when the supporting axioms hold — for example, the law that low coupling predicts cheaper change. A corollary is a proposition that follows from an axiom or theorem in a straightforward way — for example, "therefore an indirection layer that narrows an interface reduces propagation at this point." Base axioms generate the principles and guidelines teams then refine into class and interaction diagrams.
Q: The discussion mentioned Sue's axioms. Who is that? A: The rendered audio name Sue matches Nam P. Suh, whose axiomatic design theory (publications from the late 1970s, consolidated in the 1990 book The Principles of Design) defines the independence axiom and the information axiom. The two axioms — maintain independence of functional components (minimize interdependence) and minimize information content — match the session description closely and are the same pair GRASP evaluates as low coupling and high cohesion. T1 companion text and engineering design literature both cite Suh as the canonical source, so this attribution replaces the garbled audio form. The collaboration note in the course notes ("Sue and others") reflects later co-authored elaborations, but the axioms' parent theory is attributable to Suh.
Q: Should every possible variation be protected now? A: No, and the lecture is explicit that this misunderstanding costs projects dearly. Protected variations targets predicted, anticipated variation — change you can name and whose probability justifies the abstraction cost. Trying to protect against every imaginable change overloads the system with interfaces, layers, and adapters, raises information content (violating the information axiom), and lowers performance through longer paths. The designer should assess which variations are likely and worth a stable interface now, and leave speculative generalizations aside. The operational test is the razor: can you name the next concrete variant that would be added? If not, defer.
Q: The discussion on Occam's Razor was not understood. What should be remembered? A: Remember it as the simplicity penalty that keeps the other principles honest. When two designs satisfy the same requirements and the same independence concerns, the simpler one is preferred — fewer entities, fewer assumptions, fewer special cases. That preference directly supports both design axioms (keep dependence low and keep information content low) and it is the reason every added layer, adapter, or fabricated helper must earn its coupling savings. The session raises it as a check on enthusiasm for generalization.
Recap and bridge to the lecture. The two axioms — independence (keep functional components from affecting each other) and information (keep the design's information content minimal) — generate the evaluator pair low coupling / high cohesion, the repair patterns indirection and fabrication, the shielding strategy protected variations (with Liskov and Demeter as checks), and the life-cycle consequence Open-Close (extension, not modification). Together they explain why the same POS decisions recur: yearly tax rules become new polymorphic adapters behind a stable interface, persistence becomes one fabricated handler, long chains are shortened by moving responsibility inward, and clients stay closed to edit while open to new extensions. The final chapter of the lecture — how all principles connect — is therefore not a summary slide but a theorem map: respect the axioms and the good properties cluster; over-apply the theorems and the same axioms predict the cost.
Exam note: be ready to define axiom, theorem, and corollary in the design vocabulary, to name both axioms (independence and information) and to map each to its GRASP counterpart (low coupling and high cohesion / simplicity), to attribute them correctly to Nam P. Suh's axiomatic design, and to state the Open-Close principle both in words (closed for modification affecting clients, open for extension) and with a small before-and-after sketch where a new implementation is added while stays unchanged, noting the cost of over-generalization.
Exam Guidance Summary
This appendix preserves and lightly enriches the session's exam-facing signals so you can map each lecture block to a concrete question type. Treat each bullet as a required articulation — a definition, a trace, or a before-and-after sketch — not as a hint.
- Design before coding (11.1). Be ready to explain why teams evaluate several solutions and choose with shared principles rather than with personal taste or with pictures alone, and what pattern names buy a team in communication (chunking + shared learning). Strong answer: contrast two alternatives for the same POS task, name the yardsticks (coupling, cohesion), and state the winner's margin.
- Low coupling and high cohesion (11.2). Given a responsibility assignment — for example who creates Payment, who calculates total — compare two assignments and justify the better one by naming changes in coupling (fan-out count), cohesion (single-purpose test), reuse (portability without pulling collaborators), and fan-in/fan-out. Include a small numeric count (for example, 74% vs 36% touches) to make the case quantitative.
- GRASP nine (11.3). Name the five core patterns (Expert, Creator, Controller, Low Coupling, High Cohesion) and the four additional patterns (Polymorphism, Indirection, Pure Fabrication, Protected Variations), and in one sentence per pattern say which placement or stability problem each one solves and where it sits in the propose–score–repair–protect pipeline.
- Polymorphism (11.4). Study one full example such as the income-tax calculator family behind a common getTaxes operation or the shape family behind a common findArea operation. Show the polymorphic call line, the runtime binding to two concrete types, and the computed answers (for example, Rectangle 20, Square 16, Triangle 12). Say why conditional branches are the weaker choice (N edited sites versus one new class) and state the interface versus abstract class trade-off (hierarchy freedom).
- Indirection (11.5). Identify highly coupled elements with many direct edges and show where an intermediate layer or mediator reduces coupling by the relation , narrowing fan-out from many to one stable interface. Note the overlap that the mediator is often a pure fabrication and justify not stopping at direct talk when the edge count is high.
- Pure fabrication (11.6). Identify a cohesion or coupling problem that Expert would make worse (persistence scattered across Sale, ProductDescription, Customer with duplicate JDBC). Propose a fabricated helper such as a persistence handler or a table-of-contents generator, justify it through four scores — higher cohesion, lower coupling to a stable interface, higher reuse across types, and duplicate-code elimination — and sketch the before-and-after wiring (many database edges collapse to one storage interface).
- Protected variations (11.7). State the umbrella goal (isolate predicted variation behind a stable interface), state Liskov Substitution in words and in the formal form with its contract scope (pre/postconditions, invariants), and correct a Law-of-Demeter chain violation (for example, ) by moving responsibility to a close collaborator (for example,
a.doSomething()delegating inward).
- Axioms and principles (11.8). Define axiom, theorem, and corollary in the design theory vocabulary, name the independence axiom (keep functional components independent — low coupling) and the information axiom (minimize information content — high cohesion / simplicity), map each correctly to its GRASP evaluator, attribute the pair to Nam P. Suh's axiomatic design, and state the Open-Close principle as closed for modification affecting clients but open for extension, with the relation and the cost note about over-generalization violating the information axiom.
Key Industry Applications
- Point-of-sale systems — the running anchor for the whole lecture. Assignment of makePayment to Sale instead of Register (Expert + Creator + Low Coupling), and pluggable tax calculators behind one stable ITaxCalculator interface that accepts third-party vendor adapters across yearly rule changes (Polymorphism + Protected Variations + OCP). Yearly rules arrive as new classes; clients are not edited.
- Provider adapters — payment, mapping, and tax services. Domain services exposed through one interface with multiple vendor adapters selected at runtime by configuration (often behind Adapter/Strategy fabrications). Same call line, different binding per deployment; LSP guarantees the swap is silent. This is the enterprise-scale version of the electrical-plug and USB mental model.
- Persistence and reporting — the fabrication anchor. Dedicated fabricated helpers for database create, update, delete (PersistentStorage) and for generators such as table of contents, report, or export, reused across many domain types to collapse duplicate code and to keep domain classes focused on one purpose. The fabricated class is the narrow interface domain code depends on.
- Physical analogies reused in team talk. Electrical plug/socket and Universal Serial Bus as durable mental models for one interface with many pluggable implementations — the shared vocabulary that lets code review say "this needs a plug here" and be understood.
- Architectural control — layering as indirection at scale. Layered facades, application controllers, and service facades between user-interface, domain, and data layers — each an indirection fabric that keeps layers from direct, highly coupled contact and enforces the Law of Demeter across tier boundaries.
- Code-review and refactoring checks — operational proxies for the axioms. Mechanical scans for public state (information-hiding violation), long Law-of-Demeter chains (structural coupling), and large low-cohesion classes (God objects, fan-out spikes) — exactly the signals that independence and information axioms predict. Tooling that counts fan-in/fan-out on imports or sequence diagrams turns the GRASP judgment into a number the pipeline can gate on.
OODAP Lecture 11 notes · GRASP Patterns and Design Principles — Polymorphism, Indirection, Fabrication and Protected Variations
Sections Breakdown
Design is choosing among several workable solutions using shared principles, and patterns give teams a named problem-solution vocabulary to make that choice fast.
Low coupling (low dependence, low fan-out) and high cohesion (related responsibilities in one module) are the scored yardsticks that trade off and guide every GRASP decision.
GRASP is nine named responsibility-assignment patterns: five core placements (Expert, Creator, Controller, Low Coupling, High Cohesion) and four repairs (Polymorphism, Indirection, Pure Fabrication, Protected Variations).
Polymorphism handles type-based variations with one interface and many implementations selected by runtime dispatch, so new variants are new classes outside the client, not new branches inside it.
Indirection inserts a narrow stable mediator between highly coupled elements so direct edges disappear and each side depends only on the mediator.
Pure fabrication invents a behavior-centered class pure to group cohesive responsibilities when the domain expert would become incohesive or highly coupled.
Protected Variations isolates predicted variation behind stable interfaces; Liskov Substitution checks type contracts for safe substitution, Law of Demeter checks structure by forbidding long message chains.
Design theory with two axioms — independence and information — from which low coupling, high cohesion, hiding, PV, and Open-Close derive; Occam's razor penalizes unjustified complexity.
Maps each lecture block to exam question type, requiring definitions, traces, and wiring sketches.
Industry anchors: POS with Sale ownership, pluggable provider adapters, fabricated persistence, analogies, layered facades, and review scans.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Why We Design Before Implementation and How Patterns Give Us a Shared Vocabulary
Must-know: Design evaluates several solutions against principles like coupling and cohesion; UML visualizes but the choice is the design; pattern names enable fast communication.
⚠️ Top pitfall: Thinking UML drawing alone is design; first workable solution is not automatically best.
Self-check: Why is design before coding better than just coding the first working solution?
Connects to: 11.2, 11.3
Low Coupling and High Cohesion — The Yin and Yang of Software Design
Must-know: Low coupling is low dependence between modules; high cohesion is related responsibilities inside one module; balance them and never aim for zero coupling.
⚠️ Top pitfall: Chasing zero coupling or extreme cohesion; forgetting that moderate coupling is inherent in communicating objects.
Self-check: Who should create Payment — Register or Sale — and how does the answer score on coupling, cohesion, and reuse?
Connects to: 11.3, 11.6
GRASP — The Nine Responsibility-Assignment Patterns
Must-know: Name all nine GRASP patterns and state the propose-score-repair-protect pipeline: Expert/Creator/Controller propose, Low Coupling/High Cohesion score, the four repairs restructure, PV protects.
⚠️ Top pitfall: Treating GRASP as laws; reaching for Pure Fabrication before trying Expert.
Self-check: Which four GRASP patterns repair conflicts where Expert would hurt cohesion or coupling?
Connects to: 11.4, 11.5, 11.6, 11.7
Polymorphism — Handling Alternatives Without Conditionals
Must-know: Polymorphism means one interface many implementations; alternatives by type should be polymorphic ops, not conditionals; interfaces leave more hierarchy freedom than abstract classes.
⚠️ Top pitfall: Overusing polymorphism for speculative variations that will never happen; adding complexity for no gain.
Self-check: Trace getTaxes for Sale total 130 with 2020 rate 5% vs 2021 rate 7%: what class changes and what stays closed?
Connects to: 11.5, 11.7
Indirection — Decoupling Through an Intermediate Layer
Must-know: Add indirection when two elements are highly coupled; mechanism is A -> Mediator -> B narrowing dependencies; often a pure fabrication.
⚠️ Top pitfall: Adding indirection when coupling is already low; creating a God mediator.
Self-check: When should you insert a mediator and what happens to fan-out?
Connects to: 11.6, 11.4
Pure Fabrication — Invented Classes That Do Not Exist in the Domain
Must-know: Fabricate when Expert would cause overload: persistence, generators, adapters; justify by higher cohesion, lower coupling, higher reuse, less duplicate code.
⚠️ Top pitfall: Fabricating before trying Expert; creating a God fabrication hoarding unrelated behaviors.
Self-check: Why does Sale.save() with JDBC motivate a PersistentStorage fabrication?
Connects to: 11.5, 11.2
Protected Variations — Stability Through Interfaces, Liskov Substitution and Law of Demeter
Must-know: PV is umbrella; LSP: subtypes must honor supertype contract S <: T; Demeter: only talk to close connections, avoid a.getB().getC() chains.
⚠️ Top pitfall: Confusing Demeter as PV itself; satisfying type checker but breaking behavioral contract.
Self-check: Correct this Demeter violation: this.getCustomer().getOrder().getLineItem().print()
Connects to: 11.8, 11.4
Design Axioms, Theorems, Information Hiding and the Open-Close Principle
Must-know: Axiom is fundamental truth; theorem follows from axioms; independence maps to low coupling, information to high cohesion; OCP is open for extension closed for modification; attribute to Nam P. Suh and Bertrand Meyer.
⚠️ Top pitfall: Treating OCP as 'never edit the file'; protecting every speculative variation and violating information axiom.
Self-check: State both axioms and their GRASP counterparts and sketch OCP with a new tax adapter.
Connects to: 11.2, 11.7
Exam Guidance Summary
Must-know: All eight concept blocks have distinct exam prompts; know the scoring vocabulary for coupling and cohesion.
⚠️ Top pitfall: Answering with taste rather than scored yardsticks.
Self-check: List what to prepare for each of 11.2, 11.4, 11.6, 11.7.
Connects to: None
Key Industry Applications
Must-know: Map each pattern to a concrete industry use: POS, adapters, persistence, facades, code-review checks.
⚠️ Top pitfall: Giving vague 'used in engineering' without naming the mechanism.
Self-check: Name three industry uses of polymorphism from the lecture.
Connects to: None
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.