Visitor Pattern: The Doctor Who Visits Every Class
Learn the Visitor design pattern with a school health check-up story, double dispatch made simple, TypeScript and C# code, real examples, and practice.
Health check-up day at Vidya Mandir School 🩺
It is the first Monday of July at Vidya Mandir School in Jaipur — health check-up day. Dr. Kapoor arrives with her bag, signs the register at the gate, and walks from classroom to classroom. The principal, Mrs. Iyer, walks ahead of her, knocking on each door.
Watch what the doctor does in each room:
- In Class 2 (the little ones), she checks height, weight, and gives the polio booster chart a look.
- In Class 7 (the middle group), she checks eyesight carefully — too much mobile! — and posture, because of those heavy school bags.
- In Class 11 (the seniors), she checks blood pressure and talks about exam stress and sleep.
Same doctor, one visit per class, but a different check-up for each class type. And here is the part to notice: the classrooms do not change themselves for her. They just do one simple thing: open the door and say, "Welcome, doctor — we are Class 7." The doctor carries all the class-specific knowledge in her own head and bag.
Next Monday, a different visitor comes: Farhan, the school photographer. He also goes room by room, behind the same Mrs. Iyer, through the same doors. Little ones sit in two neat rows, middle schoolers stand on benches, seniors get individual portraits for the yearbook. Again: the classrooms did not change. They opened the same door and said the same words — "we are Class 7" — and the visitor did his own type-specific job.
Here is the magic to notice: the school added a completely new operation (photography) without touching a single classroom. And if next month a librarian visits to issue cards, or a sports coach visits to measure fitness — still zero changes to the classrooms. The classrooms only ever learned one skill: open the door and say who you are.
This is the Visitor pattern: keep the element classes (classrooms) stable, and put each new operation (doctor, photographer, librarian) into its own visitor object that knows what to do with each element type.
What is the Visitor pattern? 🧠
Visitor is a behavioral design pattern. The plain definition: when you have a fixed family of classes (Circle/Rectangle, or JuniorClass/MiddleClass/SeniorClass) and you keep needing new operations over all of them, do not keep adding methods to every class. Instead:
- Create a Visitor interface with one method per element type:
visitJunior(...),visitMiddle(...),visitSenior(...). - Give every element one tiny method,
accept(visitor), whose only job is to call the visit method matching its own type: insideJuniorClass,acceptcallsvisitor.visitJunior(this). - Each new operation becomes one new concrete visitor class that implements all the visit methods. The whole operation lives together in one place.
The element classes are written once and then left alone. Operations arrive forever after as new visitor classes. This is the Open/Closed Principle applied to operations.
The pattern appears in the Gang of Four book (1994) and is also known by the name of the trick that powers it: Double Dispatch. We will unpack that trick slowly below, because it is the one genuinely clever part.
One-line summary: Visitor = move the "what to do" out of the data classes and into separate operation objects; the data classes only say "we are of type X, do your X-thing on us."
The cast:
- Element interface — declares
accept(visitor)(the classroom door). - Concrete elements —
JuniorClass,MiddleClass,SeniorClass; eachacceptis one line. - Visitor interface — one
visitXmethod per element type. - Concrete visitors —
DoctorVisitor,PhotographerVisitor— one per operation.
The problem it solves 😵
Let us write check-up day the painful way. Every job becomes a method on every class:
// BAD CODE: every new school activity bloats every class
class JuniorClass {
healthCheck(): void { console.log("Height, weight, polio chart"); }
takePhoto(): void { console.log("Two neat rows, say cheese"); }
issueLibraryCards(): void { console.log("Picture books section"); }
// next month: fitnessTest()? feeReport()? magazineSurvey()? ...
}
class MiddleClass {
healthCheck(): void { console.log("Eyesight and posture check"); }
takePhoto(): void { console.log("Standing on benches"); }
issueLibraryCards(): void { console.log("Junior fiction section"); }
}
class SeniorClass {
healthCheck(): void { console.log("BP check, sleep advice"); }
takePhoto(): void { console.log("Individual yearbook portraits"); }
issueLibraryCards(): void { console.log("Reference section access"); }
}Three classes, three operations — already nine methods, and the school year has just begun. The troubles:
- Every new operation edits every class. The fitness test arrives → open all three classes, add
fitnessTest()to each. Ten element types? Ten edits per operation, forever. - Classes learn things they should not know. Why should a classroom know about photography angles and library sections?
JuniorClassshould be about students and a timetable — not about every department's business. Unrelated concerns (and their imports!) leak into core classes. - One operation is scattered across many files. The complete "photography plan" exists in three fragments in three classes. To read or change the photo logic, you hop between files and stitch it together in your head.
The method count is operations × types — it grows like a multiplication table. Watch what happens to the methods crammed into element classes as the school year adds activities, versus the Visitor version where elements keep exactly one accept each:
The alternative quick fix — a big function with type checks — is also ugly:
// ALSO BAD: type-checking ladder
function healthCheck(room: SchoolClass): void {
if (room instanceof JuniorClass) { /* ... */ }
else if (room instanceof MiddleClass) { /* ... */ }
else if (room instanceof SeniorClass) { /* ... */ }
// add a new class and the compiler will NOT remind you here
}If someone adds NurseryClass next year, this ladder silently does nothing for it. No compile error, just a missed check-up for thirty four-year-olds.
What we want: add operations freely while the set of classes stays stable — and keep each operation's logic in one tidy place. That is exactly Visitor's offer.
How it works, step by step 🛠️
- Confirm the hierarchy is stable. Visitor pays off only when element types rarely change. Classrooms: Junior, Middle, Senior — fixed for years. Good.
- Declare the Visitor interface with one method per concrete element:
visitJunior(j),visitMiddle(m),visitSenior(s). Distinct names keep things clear (languages with overloading can use one name). - Add
accept(visitor)to the element interface. Every classroom promises to receive visitors. - Implement
acceptin each element — one line each.JuniorClass.accept(v)callsv.visitJunior(this). This is the only code the pattern ever adds to elements. - Write one concrete visitor per operation.
DoctorVisitorimplements all three visit methods; so doesPhotographerVisitor. Any running totals or report buffers live as fields on the visitor. - The client walks the structure. Loop over the classrooms and call
room.accept(visitor), then read results off the visitor.
The secret engine: double dispatch 🔑
Why the little accept dance? Why not call visitor.visitJunior(room) directly? Because of a quiet problem: the client usually holds rooms as the general type SchoolClass and does not know which concrete class each one is. The compiler cannot pick visitJunior vs visitSenior from a variable typed SchoolClass.
The right behaviour depends on two runtime types at once — the element's type and the visitor's type. Ordinary method calls select by only one type (the receiver). So Visitor chains two ordinary calls:
room.accept(visitor)— selected by the room's real type. We land inside, say,JuniorClass.accept, wherethisis definitely aJuniorClass.visitor.visitJunior(this)— selected by the visitor's real type. Doctor or photographer, the right one answers.
Think of it as the school's two-question protocol. Mrs. Iyer knocks; the room answers "we are Class 2" (first dispatch — by room type); then the visitor opens the matching page of her own checklist (second dispatch — by visitor type). Here is the handshake traced:
Two single dispatches, chained, simulate one double dispatch — with zero instanceof checks. The type system does all the choosing, which is why Visitor is type-safe: add NurseryClass with its accept, and every visitor fails to compile until it handles the new case. The compiler becomes your checklist.
College corner — double dispatch, properly: mainstream OO languages (Java, C#, C++, TypeScript) are single-dispatch: the method body is chosen at runtime by exactly one argument, the receiver before the dot. Visitor is the standard workaround to get multiple dispatch behaviour out of single-dispatch machinery. Some languages have real multiple dispatch built in — CLOS (Common Lisp) and Julia select a method by the runtime types of all arguments, so they simply do not need the Visitor pattern. C# offers a halfway house with dynamic, and modern pattern matching (see below) is another escape route. In an exam, the crisp sentence is: Visitor simulates double dispatch by chaining two single dispatches — accept dispatches on the element, visit dispatches on the visitor.
Real-life code example 💻
The full school day in TypeScript — two visitors doing completely different jobs on the same classroom objects:
// ---------- Elements (stable — written once) ----------
interface SchoolClass {
accept(v: SchoolVisitor): void;
}
class JuniorClass implements SchoolClass {
constructor(public section: string, public students: number) {}
accept(v: SchoolVisitor): void {
v.visitJunior(this); // "we are juniors — do your junior thing"
}
}
class MiddleClass implements SchoolClass {
constructor(public section: string, public students: number) {}
accept(v: SchoolVisitor): void {
v.visitMiddle(this);
}
}
class SeniorClass implements SchoolClass {
constructor(public section: string, public students: number) {}
accept(v: SchoolVisitor): void {
v.visitSenior(this);
}
}
// ---------- Visitor interface ----------
interface SchoolVisitor {
visitJunior(j: JuniorClass): void;
visitMiddle(m: MiddleClass): void;
visitSenior(s: SeniorClass): void;
}
// ---------- Visitor 1: Dr. Kapoor ----------
class DoctorVisitor implements SchoolVisitor {
private checked = 0; // visitors can accumulate state!
visitJunior(j: JuniorClass): void {
console.log(`Class ${j.section}: height, weight, polio chart (${j.students} kids)`);
this.checked += j.students;
}
visitMiddle(m: MiddleClass): void {
console.log(`Class ${m.section}: eyesight and posture check (${m.students} kids)`);
this.checked += m.students;
}
visitSenior(s: SeniorClass): void {
console.log(`Class ${s.section}: BP check and sleep advice (${s.students} kids)`);
this.checked += s.students;
}
report(): void {
console.log(`Doctor's report: ${this.checked} students checked.\n`);
}
}
// ---------- Visitor 2: Farhan (NEW operation, ZERO element edits) ----------
class PhotographerVisitor implements SchoolVisitor {
private photos = 0;
visitJunior(j: JuniorClass): void {
console.log(`Class ${j.section}: two neat rows, say cheese!`);
this.photos += 1; // one group photo
}
visitMiddle(m: MiddleClass): void {
console.log(`Class ${m.section}: standing on benches, group photo`);
this.photos += 1;
}
visitSenior(s: SeniorClass): void {
console.log(`Class ${s.section}: individual yearbook portraits`);
this.photos += s.students; // one photo per senior
}
report(): void {
console.log(`Photographer's report: ${this.photos} photos taken.\n`);
}
}
// ---------- The school (client) ----------
const classrooms: SchoolClass[] = [
new JuniorClass("2-A", 30),
new MiddleClass("7-B", 35),
new SeniorClass("11-C", 25),
];
console.log("--- Monday: health check-up day ---");
const doctor = new DoctorVisitor();
for (const room of classrooms) room.accept(doctor); // double dispatch!
doctor.report();
console.log("--- Next Monday: photo day ---");
const photographer = new PhotographerVisitor();
for (const room of classrooms) room.accept(photographer); // same rooms, new job
photographer.report();The output:
--- Monday: health check-up day ---
Class 2-A: height, weight, polio chart (30 kids)
Class 7-B: eyesight and posture check (35 kids)
Class 11-C: BP check and sleep advice (25 kids)
Doctor's report: 90 students checked.
--- Next Monday: photo day ---
Class 2-A: two neat rows, say cheese!
Class 7-B: standing on benches, group photo
Class 11-C: individual yearbook portraits
Photographer's report: 27 photos taken.Three things to underline with a red pen:
- The same three classroom objects served two totally different operations, and were never edited. A
LibrarianVisitornext month means one new class, nothing else. - No
instanceof, no casts, no type ladders. The loop holds rooms as plainSchoolClass. The double-dispatch handshake pickedvisitJuniorvsvisitSeniorautomatically. - Each visitor accumulated its own state — the doctor counted students, the photographer counted photos. The visitor object is a natural home for running totals, buffers, and error lists collected during a traversal.
From a single classroom's point of view, a visit day is a tiny, calm state machine. The room never learns the visitor's business — it just hosts and returns to lessons:
The same idea in C# 📐
The classic graphics version, very short — shapes that can report area or export themselves, without owning either skill:
interface IShape { void Accept(IShapeVisitor v); }
class Circle : IShape
{
public double Radius;
public void Accept(IShapeVisitor v) => v.VisitCircle(this);
}
class Rect : IShape
{
public double W, H;
public void Accept(IShapeVisitor v) => v.VisitRect(this);
}
interface IShapeVisitor
{
void VisitCircle(Circle c);
void VisitRect(Rect r);
}
class AreaVisitor : IShapeVisitor
{
public double Total;
public void VisitCircle(Circle c) => Total += Math.PI * c.Radius * c.Radius;
public void VisitRect(Rect r) => Total += r.W * r.H;
}
class XmlVisitor : IShapeVisitor
{
public string Xml = "";
public void VisitCircle(Circle c) => Xml += $"<circle r='{c.Radius}'/>";
public void VisitRect(Rect r) => Xml += $"<rect w='{r.W}' h='{r.H}'/>";
}
// Usage
var shapes = new List<IShape> { new Circle { Radius = 2 }, new Rect { W = 3, H = 4 } };
var area = new AreaVisitor();
foreach (var s in shapes) s.Accept(area);
Console.WriteLine($"Total area: {area.Total:F2}"); // 24.57
var xml = new XmlVisitor();
foreach (var s in shapes) s.Accept(xml);
Console.WriteLine(xml.Xml); // <circle r='2'/><rect w='3' h='4'/>Geometry classes stay pure geometry. XML formatting and area math each live in one cohesive class. Small note for curious seniors: languages with pattern matching (C# switch expressions on type, Rust match, Swift enums) can express the same two-type selection with a switch instead of accept — which is why those communities sometimes skip the classic Visitor.
And once more in Python: a baby compiler 🐍
Visitor's true home is the compiler. Here is a five-minute taste: a tiny arithmetic expression tree with two visitors over the same nodes — one evaluates, one pretty-prints. This is, in miniature, exactly how real compilers and linters work.
# ---------- Elements: a tiny AST (stable family) ----------
class Num:
def __init__(self, value): self.value = value
def accept(self, v): return v.visit_num(self)
class Add:
def __init__(self, left, right): self.left, self.right = left, right
def accept(self, v): return v.visit_add(self)
class Mul:
def __init__(self, left, right): self.left, self.right = left, right
def accept(self, v): return v.visit_mul(self)
# ---------- Visitor 1: evaluate the tree ----------
class EvalVisitor:
def visit_num(self, n): return n.value
def visit_add(self, a): return a.left.accept(self) + a.right.accept(self)
def visit_mul(self, m): return m.left.accept(self) * m.right.accept(self)
# ---------- Visitor 2: print the tree (new operation, zero node edits) ----------
class PrintVisitor:
def visit_num(self, n): return str(n.value)
def visit_add(self, a): return f"({a.left.accept(self)} + {a.right.accept(self)})"
def visit_mul(self, m): return f"({m.left.accept(self)} * {m.right.accept(self)})"
# The expression (2 + 3) * 4 as a tree
tree = Mul(Add(Num(2), Num(3)), Num(4))
print(tree.accept(PrintVisitor())) # ((2 + 3) * 4)
print(tree.accept(EvalVisitor())) # 20Notice the recursion: for tree-shaped elements, the visitor itself walks into children by calling child.accept(self). Tomorrow you can add a TypeCheckVisitor or an OptimizeVisitor — the Num, Add, Mul classes never change. That is precisely why every serious compiler keeps its AST stable and ships visitors by the dozen.
Where you see it in real software 🌍
Visitor's home turf is anywhere a stable tree of node types meets an ever-growing list of operations:
- Compilers and ASTs (the canonical example). A compiler parses code into an Abstract Syntax Tree — a fixed family of node types (if-node, loop-node, expression-node). Then pass after pass walks that tree: type checking, optimization, code generation, pretty printing. Each pass is a visitor. The .NET compiler platform Roslyn ships
CSharpSyntaxVisitorandCSharpSyntaxRewriterbase classes; javac has com.sun.source tree visitors; parser generators like ANTLR generate a visitor interface for your grammar automatically. - Linters and code formatters. ESLint rules subscribe to AST node types and get called as the traversal enters and leaves each node — a visitor-style walk over your JavaScript. Babel plugins are literally written as visitor objects with one handler per node type. TypeScript's compiler API exposes
ts.forEachChildand transformer visitors for the same job. - Document and scene-graph exports. A document model (sections, paragraphs, images, tables) is a stable Composite tree; PDF export, HTML export, word-count, and spell-check are visitors walking it. 3D engines do similar passes over scene graphs.
- Serializers and report writers over fixed object models — pricing engines, insurance rule passes, billing audits — each new business report becomes a new visitor instead of more methods on entity classes.
- Open-source code to read. Start with the tidy iluwatar visitor example, then peek at a real ESLint rule on GitHub — you will recognize the visit-per-node-type shape immediately.
Tallied up, the wild population of visitors looks like this:
When to use it and when not to 🤔
| Situation | Use Visitor? |
|---|---|
| Element types are stable, but new operations keep arriving | ✅ Yes — the sweet spot (ASTs, document models) |
| One operation must behave differently per concrete type | ✅ Yes — the whole operation stays in one class |
| You must keep serialization/reporting/pricing OUT of core data classes | ✅ Yes — separation of concerns |
| You need to accumulate results across a traversal (totals, buffers, error lists) | ✅ Yes — visitor fields hold them naturally |
| New element types are added often | ❌ No — every new type forces edits in every visitor |
| There is only one operation, and it rarely changes | ❌ No — a plain method on each element is simpler |
| Your language has exhaustive pattern matching over closed types | ❌ Maybe not — a match expression may be cleaner |
| Visitors would need deep access to element private data | ⚠️ Careful — you may be forced to expose internals |
That first ❌ row deserves its own table — the famous expression problem. Software grows along two axes, and each style makes only one axis cheap:
| Add a new operation | Add a new type | |
|---|---|---|
| Plain methods on each class | Hard — edit every class | Easy — write one new class |
| Visitor | Easy — write one new visitor | Hard — edit every visitor |
College corner — the expression problem, properly: this trade-off has a name and a long academic history; Philip Wadler popularized the term in 1998. The challenge: extend a program with both new types and new operations, without modifying existing code and while keeping static type safety. Plain OO methods make types cheap and operations costly; Visitor (like functional pattern matching over a closed set of data types) makes operations cheap and types costly. Neither plain OO nor Visitor solves the problem — each picks a side. Genuine solutions need fancier machinery (type classes in Haskell, traits with default methods, object algebras, multimethods). For an exam or interview, the gold sentence is: Visitor converts the expression problem's hard axis from operations to types — choose it only when your project grows along the operations axis.
Place your own project on this map before you commit:
Common mistakes students make 🚧
The number one mistake: using Visitor when the element family is still growing. If you expect a NurseryClass next month and a SportsClass after that, every addition breaks every visitor. That compile error storm is sometimes useful (the compiler forces every operation to handle the new case), but if it happens every sprint, Visitor was the wrong pick — plain polymorphic methods grow types cheaply.
More traps:
- Skipping
acceptand type-checking instead. Writingif (room instanceof JuniorClass) visitor.visitJunior(room)in the client throws away the pattern's type safety. The whole point ofacceptis that no one ever inspects types manually. acceptmethods that do real work.acceptmust be one line:v.visitX(this). Students sometimes put business logic in it, which smears the operation back into the elements.- Forgetting who owns traversal. For flat lists, the client loops. For trees (Composite, like our Python AST), the visitor or the element recurses into children. Decide once, document it, and do not duplicate traversal in both places.
- Stateless visitors recreated per element. Create the visitor once, feed all elements through it, then read its accumulated result. Creating a fresh
AreaVisitorinside the loop gives you many totals of one shape each. - Prying elements open. If a visitor needs twelve getters added to every element, the design is fighting you. Either pass the needed data into
visitmethods or reconsider whether the operation belongs inside the element after all.
Memory trick for accept: the element is only answering one question — "who are you?" — and it answers by calling the visit method named after itself. Everything else belongs to the visitor.
Compare with cousins 👯
| Pattern | What is extracted? | How is behaviour chosen? | Typical use |
|---|---|---|---|
| Visitor | A whole family of type-specific behaviours (one per element) | Double dispatch via accept | New operations over stable hierarchies |
| Plain methods | Nothing — behaviour lives in each class | Single dispatch (normal virtual call) | Stable operations, growing types |
| Strategy | One algorithm behind one interface | Client installs one object | Swappable single behaviour |
| Iterator | The traversal itself | Caller pulls elements one by one | Walking, with logic kept in the caller |
| Composite | Tree structure of elements | — | The structure visitors usually walk |
The comparison students mix up most: Visitor vs simply adding methods. Both give per-type behaviour. The difference is purely where the code lives and what is cheap to add:
- Methods on elements: each class carries its own
healthCheck(). Adding a type is easy; adding an operation means editing every class. Operation logic is scattered. - Visitor: each operation carries its per-type cases together. Adding an operation is easy; adding a type means editing every visitor. Operation logic is cohesive.
And two friendly partnerships: Composite + Visitor is the textbook pair — visitors walk composite trees (documents, ASTs) with the recursion living in accept or in the visitor itself, as our Python compiler showed. Iterator + Visitor also cooperate: the iterator hands out elements, and each element accepts the visitor.
The whole pattern on one page 🗺️
If you can redraw this mind map from memory — and explain the double dispatch branch out loud — you own the pattern:
Quick revision box 📦
+--------------------------------------------------------------+
| VISITOR PATTERN — REVISION |
+--------------------------------------------------------------+
| WHAT : Add new operations over a stable class family |
| without editing those classes |
| ACTORS : Elements with accept(v) + Visitor interface |
| (one visitX per element) + concrete visitors |
| KEY MOVE : element.accept(v) -> v.visitX(this) |
| = DOUBLE DISPATCH (two chained virtual calls) |
| ACCEPT : Always ONE line; never holds business logic |
| TRADE-OFF: Operations cheap to add, element types costly |
| (the expression problem — Visitor picks a side) |
| AVOID IF : Element hierarchy keeps growing |
| EXAMPLES : Compiler AST passes, ESLint/Babel rules, Roslyn |
| visitors, document exporters |
+--------------------------------------------------------------+Practice exercise ✍️
Time to be the visitor yourself:
- Third visitor, zero edits. Take the school code from this post and add a
LibrarianVisitorthat issues cards: picture-book cards for juniors, fiction cards for middles, reference access for seniors, counting total cards issued. Rule of the game: you may not touchJuniorClass,MiddleClass, orSeniorClass. If you feel the need to — re-read the pattern! - Electricity meter reader. Model
House,Shop, andFactorybuildings, each with aunitsConsumedfield and anaccept. Write aBillingVisitor(₹5/unit for houses, ₹8 for shops, ₹10 for factories, accumulating the total bill) and anInspectionVisitor(prints a different safety checklist per building type). Run both over the same list of buildings. - Feel the trade-off (thinking task). Now add a fourth building type,
School, to exercise 2. Write down every file you had to change. Then answer in two sentences: if new building types arrived every month, would you keep Visitor or switch back to plain methods? Which axis of the expression problem does your project grow along? - College stretch. Extend the Python baby compiler with a
Subnode and aTypeCheckVisitorthat rejects trees deeper than three levels. First add the visitor (easy axis), then the node (hard axis — count how many classes you had to open). Write one sentence connecting your experience to the expression problem table above.
Frequently asked questions
- What is the Visitor pattern in one line?
- Visitor lets you add a brand-new operation to a family of classes without editing those classes. The operation lives in one separate visitor object, and each element simply 'accepts' the visitor and calls the visit method matching its own type.
- Why does every element need an accept method?
- Because the client usually holds elements by their common interface and does not know each one's concrete type. Inside accept, the element DOES know its own type, so it can call the exact matching visit method. This two-step trick is called double dispatch.
- What is double dispatch in simple words?
- The behaviour that runs depends on TWO types at once: the element's type and the visitor's type. Ordinary method calls pick behaviour by only one type. Visitor chains two ordinary calls — element.accept(visitor) then visitor.visitX(element) — to select by both.
- When is Visitor a bad idea?
- When the family of element classes keeps growing. Every new element forces you to edit every visitor and add one more visit method. Visitor shines when the element types are stable and the operations keep growing — like compiler ASTs.
- Is Visitor the same as just looping over objects?
- No. A loop still has to ask 'what type is this?' with if-else or casts. Visitor removes those checks: the right visit method is chosen by the type system automatically, and each whole operation stays together in one class.
Further reading
Related Lessons
State Pattern: The Fan That Changes Its Mood
Learn the State design pattern with a ceiling fan regulator story, simple TypeScript and C# code, state diagrams, real software examples, and practice.
Strategy Pattern: Cycle, Bus, or Auto — You Choose
Learn the Strategy design pattern with a simple school travel story, easy TypeScript and C# code, runtime swapping, real examples, and practice tasks.
Template Method Pattern: Chai and Coffee, Same Steps
Learn the Template Method design pattern with a chai and coffee story, easy TypeScript and C# code, hooks, diagrams, real examples, and practice tasks.
Composite Pattern: Boxes Inside Boxes — Treat One Thing and Many Things Alike
Learn the Composite pattern with boxes inside a courier parcel. Treat one item and a full group the same way, and add totals easily with simple recursion.