Design Patterns — Gang of Four Solutions
Prerequisite Knowledge
This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.
Previously Covered in This Subject
- 1.10 Design Patterns — covered in Lecture 1: Object-Oriented Analysis and Design
- 1.8 Interfaces — covered in Lecture 1: Object-Oriented Analysis and Design
- 8.1 Object-Oriented Analysis as Decomposition of the Problem Domain — covered in Lecture 8: Object-Oriented Analysis and the Domain Model
- 9.1.4 Industry Applications — covered in Lecture 9: Object Oriented Design with UML Interaction Models
- 10.1.5 Industry Applications — covered in Lecture 10: Designing Object Systems with GRASP and Interaction Diagrams
- 11.1 Why We Design Before Implementation and How Patterns Give Us a Shared Vocabulary — covered in Lecture 11: GRASP Patterns and Design Principles — Polymorphism, Indirection, Fabrication and Protected Variations
- 11.4.7 When Not to Use Polymorphism — Avoiding Futuristic Over-Engineering — covered in Lecture 11: GRASP Patterns and Design Principles — Polymorphism, Indirection, Fabrication and Protected Variations
- 12.1.4 Industry Applications — covered in Lecture 12: Object Oriented Design Principles and UML Modeling
Design Patterns — Gang of Four Solutions
This lecture builds the catalogue habit behind the Gang of Four design patterns — what a pattern is, why it spreads as shared vocabulary, how each catalogue sheet is documented, where patterns sit between language mechanics and system architectures, and the three GoF families that organise the 23 patterns. It then walks five canonical patterns and their failure mirror in depth: Prototype (cloning from a model with Cloneable and shape caches), Singleton (one instance via a private constructor), Composite (uniform part–whole with MediaClip/Sequence), Observer (one-to-many notification and MVC/JFrame listeners), Facade (single entry over scanner–parser–builder–generator), Adapter (interface translation via IconAdapter), and Anti-Patterns (named bad solutions and multiple-choice elimination). The closing guidance shows how to combine several patterns in one exam design and where these ideas live in industry code such as Swing, Linux pipelines and compilers.
13.1 Foundations of Design Patterns
13.1.1 What a Design Pattern Is
Hook — Why not start from a blank page every time? Imagine every dressmaker invented a new cut for every customer, or every builder redesigned a bridge from first physics. You would waste months and still repeat old mistakes. What if you could name a time-tested shape — say a suspension bridge — and borrow decades of lessons instantly? That shortcut is exactly what a design pattern gives a software designer.
A design pattern — a named, proven, general solution to a recurring design problem together with the context in which it works — is the central idea of this lecture. You do not design from scratch each time. You reuse a good solution that has already solved many similar problems and has been proven in real systems. Because the solution has been used in hundreds of instances over many years, reusing it saves time and improves reliability.
The term became widely known after the book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, published in 1994–1995. The authors are jointly called the Gang of Four or GoF. Grady Booch, a pioneer of object-oriented design, wrote the foreword and the book is still described in the lecture as the Bible of design patterns. Its lasting contribution is not a library of code to copy but a way to document the design knowledge that lives in the minds of experienced designers.
Formal idea — Pattern as a reusable design pair. A pattern records four things together so the lesson is transferable:
- A recurring problem that appears in many contexts, not a one-off requirement.
- The context — when to apply it, what forces are at play, what constraints limit you.
- A general solution structure — named roles (classes, objects, interfaces), their responsibilities, their collaborations and how they communicate, shown with UML class and interaction diagrams plus structured pseudocode.
- Consequences and trade-offs — advantages, liabilities, effect on coupling, cohesion, reusability and ease of change.
A GoF definition often repeated in the session and in the standard texts is: a description of communicating objects and classes customised to solve a general design problem in a particular context. It is a description, not finished code. You keep the shape and the collaboration, change names and details, and fit it to your classes in C++ (abstract classes) or Java (interfaces).
Think of learning any designed thing. Every built thing has a design behind it. How do you learn design? In other fields you collect problems and proven solutions and study them as pairs. In mathematics you study theorems together with worked problems and their solutions. In building trades you keep catalogues, working drawings and detail drawings that record how a problem was solved. Design patterns carry that same habit into software: record the problem, the context and the working solution so others can learn from it. Without that catalogue, knowledge stays tacit in one designer's head and dies when they leave the team.
An everyday parallel helps. When you tell a dress seller "gown" or "plazo-cut saree," a large bundle of information travels with that one name — silhouette, seams, drape, typical fabric — without a ten-minute description. The same is true when a reviewer says "we will use Factory here" — listeners immediately know the problem type, the shape of the solution and its rough trade-offs.
13.1.2 Why Learning Design Is Hard
Becoming a good designer by personal experience alone takes a very long time. The lecture uses a vivid building-architect analogy:
Intuition — The 15-to-20 buildings rule. Imagine a young civil engineer who has designed one footbridge that was actually built and walked on. Would you trust them to design a new university campus? The profession says no. Trust comes only after they have designed about 15 to 20 bridges or buildings that were built, used in the real world, tested by weather and load, and repaired after failures. Only after many implemented and tested solutions does a person become an expert. That path learns by doing and by living through failures and fixes.
Software design works the same way, but products cannot wait 20 projects. Patterns give you a shortcut: instead of waiting to build 20 systems yourself, you study the experience borrowed from others and reuse what has already been shown to work.
Whenever you design, you look at alternate solutions and choose the best fit for the particular problem at hand. A creative designer often takes a known basic solution and adds a small variation on top of it. That habit exists in every engineering field and in creative work too — from dress making to car styling to graphics. The mental step is identical: learn the recurring forms, then adapt them. This is why the lecture stresses that patterns do not make you less creative — they give you a stronger starting point so your creativity goes to the variation, not to reinventing the base.
Scope — What patterns cannot do. A pattern does not replace domain understanding or requirements analysis. It helps only after you have understood what to build and are deciding how to structure objects to achieve it. If the problem is new and has never been seen before, no catalogue helps; you must invent. Patterns also assume object-oriented decomposition is appropriate — they add little to a purely procedural or data-flow problem.
If you miss this point you will treat patterns as magic templates. They are not. They are borrowed experience, useful only when the context matches.
13.1.3 Patterns Beyond Software
The lecture stresses that patterns are not limited to code. The word was deliberately borrowed from the wider world of repeatable forms.
In clothing there are named dress patterns: a gown, a saree, and within sarees many variations such as plazo cuts and draped forms. When you tell a seller the name, a large amount of information moves with that one name. In cars there are named body patterns such as hatchbacks and SUVs — each name carries proportions, door layout, cargo intent and typical customer. In machine learning the field itself is often called pattern recognition, and the well-known book Pattern Recognition and Machine Learning by Christopher Bishop carries that same use of the word. The whole world, as the lecture puts it, is full of patterns and repetitions.
This breadth is kept in the notes for a reason: it shows why the word pattern was chosen. It points to a repeatable form, not to a one-off trick.
Concrete mapping — Dress name to software name.
| Everyday | What the name carries | Software parallel |
|---|---|---|
| Seller hears "gown, plazo cut, size M" | Silhouette, seam lines, fabric drape, stitching cost | Developer hears "Factory" — creation separated, client coded to interface, subclass decides concrete product |
| Buyer hears "SUV" | Height, five doors, high clearance, cargo space | Developer hears "Observer" — one subject, many views, automatic notification, subject knows only observer interface |
In both cases the single name replaces a paragraph of description and brings an implicit promise: this form has worked elsewhere.
Where the analogy breaks: a dress pattern can be cut almost literally, fabric for fabric. A software pattern cannot be copied line-for-line; you must adapt names, method signatures and object lifetimes to your context. The repeatable part is the collaboration, not the code text.
13.1.4 The Core Questions of This Material
Two threads run through the entire session and will shape your exam preparation:
- What are the benefits of using patterns and why have they become the normal way to record design?
- How do you learn patterns so you can apply several of them together to a real problem and argue that the combined solution will work?
The second thread is the harder one. The lecture is explicit:
Exam note: Expect a real-world problem where you must identify several relevant patterns, combine them in one object-oriented design, show the arrangement with class diagrams and sequence or interaction diagrams plus pseudocode, and justify why the resulting design works. Individual pattern names, their intent, the problem they solve, the context they need and how the solution is shown in diagrams all matter for that task. Practise placing two to three patterns together rather than hunting for a single perfect pattern.
Pitfalls — How students stumble here.
- Treating pattern names as decoration. Writing "we used Factory" without showing which class is the factory, what interface it returns, and why creation needed indirection earns little credit.
- Confusing breadth with depth. Knowing all 23 names but being unable to draw the collaboration for three core patterns fails the exam format described.
- Ignoring context. Proposing Observer when the problem has no one-to-many or no need for automatic updates, just because you remember the diagram, shows you did not match context to intent.
Q & A — Two discussions that anchored this foundation.
Q: What are the benefits of using design patterns? Can you list a few? A: You can deliver a product within time by reusing a proven solution that has already solved hundreds of similar problems. You build on documented experience. You get systematic documentation that makes a design more reusable and makes it easier to compare alternate designs. Most visibly, patterns give you a shared vocabulary so designers, programmers, implementers and clients can talk about a design with a single name and be sure they mean the same solution and the same trade-offs — exactly like saying gown in a shop.
Q: Can you name dress patterns the way you would name software patterns when you talk to a seller? A: Yes. Names like gown, saree and plazo carry the full form with them and move information quickly. In software, saying factory pattern or observer pattern works the same way: listeners at once know the problem family, the intent and the rough structure, and they can trust that the basic solution has worked elsewhere. Several students asked this in different words; the answer was consolidated here.
Visual to keep in mind: picture a workshop wall covered with pinned catalogue sheets. Each sheet has a bold title (the pattern name), a short paragraph of when it applies, a UML sketch of two to four collaborating classes, and a bullet list of consequences at the bottom — half in green (gains) and half in amber (costs). That wall is the habit the GoF book made standard.
The handoff to the next section: once you accept that patterns are borrowed experience recorded as name + context + solution + consequences, the next question is how they help you day to day and what they cost — the focus of 13.2.
Recap — Foundations. A pattern is a named, proven, contextual solution pair, not a code snippet. The GoF book by Gamma, Helm, Johnson and Vlissides (1994–95, foreword by Grady Booch) made this catalogue the shared standard. Learning design through patterns is a shortcut for the 15-to-20-built-projects path to expertise. The word pattern is deliberately broad — dress, car, ML and building patterns all share the same idea of a repeatable form. Your exam task will require combining several patterns with diagrams and justification.
13.3 How a Pattern Is Described and How to Learn It
13.3.1 The Minimal Documentation of a Pattern
A pattern description at a minimum contains four parts. The lecture repeats this checklist because it is also your revision checklist for each of the 23 GoF patterns.
- Pattern name — a short, memorable name that acts as vocabulary. The name must be distinctive enough to recall the whole solution; that is why Facade, Adapter and Observer are chosen as images, not abstractions.
- Problem description — starts with intent (the purpose, what the pattern aims to achieve) and context (the situation and kind of problem in which it applies), then says when to apply it and what general problem it addresses. This part answers "Do I have this problem, here, now?"
- Solution — how the solution is structured, most often shown with UML class diagrams and interaction or sequence diagrams plus general structured pseudocode. The solution is given in terms of communicating classes and objects: responsibilities, interfaces, collaborations and ordering of messages. It is a shape to adapt, not code to copy.
- Consequences and trade-offs — benefits, liabilities, and notes on how the pattern affects reuse, change, coupling and cohesion. This part answers "What do I gain and what do I pay?"
Other fields often included — especially in the GoF book — are motivation, applicability, participants (named roles such as Subject, Observer, Component, Leaf), collaborations (how roles talk), implementation notes, sample code, known uses and related patterns. Headings such as classification, intent, motivation, structure, participants and collaborations are common in the GoF catalogue and are worth recognising so you can navigate the book quickly.
Solution language matters — interfaces, not concrete classes. Patterns are written as general solutions in terms of interfaces. In C++ the GoF book shows the structure with abstract classes and pure virtual operations; in Java the same idea appears as interfaces and implementations. The important design move underneath is separation of concerns and separation of interface from implementation, so later changes stay local. Because clients code to an interface, the implementation behind that interface can change, be replaced, or be adapted without rippling outward. Maintenance becomes easier because the interface that clients use stays steady while the hidden part evolves. That is also why many GoF sample diagrams declare operations on Shape, ITaxCalculatorAdapter, ISalePricingStrategy and similar abstract types, not on concrete classes.
A one-sentence test: if you can point to the interface that the pattern introduces and name the two sides it decouples, you have understood the solution at the right level of abstraction.
A concrete habit to build: for each pattern you revise, open the GoF contents, locate its Intent, read one paragraph of Motivation, sketch its Structure diagram from memory, list Participants with one-line roles, and then write two consequences — one gain, one cost — without looking.
13.3.2 Two Ideas You Must Hold When You Study Each Pattern
When you study any single pattern, hold exactly two ideas first, before diagrams:
- the intent — the purpose of the pattern, what it aims to achieve, and
- the context — the situation and the kind of problem in which it applies.
There is no simple formula or decision tree that picks a pattern for you. The lecture is explicit on this: you learn to recognise the context, recall the intent, and then map the solution structure to your problem. Pattern selection is recognition, not calculation.
Pitfall — Diagram-first study. Students often memorise class diagrams first and then try to match a problem to a diagram. That reverses the intended order. A diagram that looks similar can hide a different context and trade-off. Start with when and why (intent + context), then use the diagram as a check that the solution fits. On the exam, the opening sentence of a pattern answer should name intent and context before it draws.
Exam note: During study, for each pattern note its name, its intent, the problem it solves, the context where it fits, how its solution is shown in class and sequence diagrams, and what its consequences are. Then practise using several patterns together in one problem and justify how the combination works. Examiners allocate marks across correct pattern choice, correct diagram shape and coherent justification — all three must be present.
13.3.3 A Practical Way to Learn
This programme has no later separate course that will repeat these patterns at this depth. Everything in the syllabus — all 23 GoF patterns and many related forms — is meant to be learned now. Since they are now widely documented, the practical advice given in the lecture is concrete and worth following literally:
- Read about a pattern on the web — intent, context and consequences, not just code.
- Download code for it and run it. Step through the calls in a debugger.
- Watch how the collaborations work — who creates whom, who holds whom, who notifies whom.
- Try it in a different situation. Open a searched example (for instance, the net-standard Shape–Circle–Rectangle for Prototype), find the shape of the pattern, and then adapt it with small changes to your own problem rather than copying line for line.
The example often mentioned is the Swing library in Java for graphical user interfaces. Swing uses many of the GoF patterns at once, so working with Swing is described as a way to meet many patterns inside one real library — observer for event listeners, composite for components and containers, iterator for collections, factory for creation, singleton for toolkit instances, adapter for wrappers such as IconAdapter, and facade where a component hides a subsystem. By using Swing widgets, lists, tables and event sources you encounter observer, composite, factory, singleton, iterator, adapter and others in production code, not in toy snippets.
Worked study loop — One evening with Prototype. (a) Read intent: create new objects by cloning a prototypical instance without the client knowing the concrete class. Context: number and kind of objects not known until runtime, user controls the clone count. (b) Search "prototype pattern Shape Circle Rectangle shape cache" — the standard example also used in the lecture. Download a Java implementation with Shape.clone() and a Map<String, Shape> registry. Run it: put a Circle(10) and Rectangle(5,8) as prototypes, call clone, change the radius on the clone and draw both — confirm the prototype is unchanged. (c) Now adapt it: replace Circle/Rectangle with your domain objects — for instance, a LessonTemplate prototype for your app — and keep the same clone through the common interface. That adaptation step is the exam skill.
Q: Is there any other material we should read beyond what was discussed here? A: For now learn the individual patterns with interest and with code. Read their motivation and their known uses — known uses show you the real contexts where the pattern earned its place. The benefits will appear in your later career when you recognise a context in the wild and can reach for the right pattern quickly. That recognition ability is what opens up thinking about alternate solutions, which the exam rewards when you justify a combination of patterns. All 23 patterns are freely documented; the GoF book with C++ examples and their direct translation to Java interfaces is the core reference.
Visual to keep: picture your study table with two columns. Left column: a handwritten card per pattern with name at top, intent in one sentence, context in one sentence, tiny UML sketch, two trade-offs. Right column: a laptop running a Swing demo where you can set a breakpoint in notifyObservers, clone, or paintComponent and see the pattern fire. When the left card predicts what the right execution shows, you have learned that pattern.
Recap — Reading and learning a pattern. The minimal sheet is name → problem (intent + context + when to apply) → solution (UML + pseudocode in terms of communicating interfaces) → consequences. Study intent and context before diagrams, then practise with code: read, run, trace, adapt. Swing is the living laboratory where many patterns meet; use it as your practice ground.
13.4 Granularity — Where Patterns Sit
13.4.1 Low-Level Templates You Can Copy Directly
At the lowest level are programming constructs such as for loops, while loops and if statements. These are fine-grained. You can apply them directly and reuse them with almost no change. A for loop that traverses an array is the same in many programs. They are not called design patterns because they are not contextual design decisions — they are language mechanics.
Think of them as hand tools in a workshop: hammer, saw, screwdriver. Every builder uses them, but the design of the house is not captured in the choice of a hammer. The lecture draws this line deliberately so you do not chase patterns where mechanics suffice.
Scope — Not every reusable fragment is a pattern. If you can copy-paste without adapting and without weighing trade-offs, it is not a pattern — it is a template or idiom. A pattern should leave you with a deliberate adaptation decision and a cost to justify.
13.4.2 Medium-Level Patterns You Adapt
Design patterns sit at a medium level of granularity and at a medium level of abstraction. They are more general than a single loop but more concrete than a whole system architecture. They are defined as contextual solutions to recurring design problems that can be used with minimal adaptation. You customise them to your situation. They are not drop-in code that you cut and paste. You keep the shape and the collaborations, change names and details, and fit the solution to your classes.
This is why patterns are described as general, broadly applicable, repeatable solutions that have been used in many systems over many years. They are not finished designs ready to turn into code without thought. The published form is often pseudocode plus diagrams; you transform the pattern into the problem and then into code for your language, whether C++ or Java.
Intuition — Sewing pattern vs. dress. A sewing pattern is not a dress. It is a tissue-paper outline with darts and seam allowances that you lay on your fabric, cut to your size, and adapt for your cloth. The outline is general; the dress is specific. Software patterns work identically: the GoF diagrams are the tissue-paper outline; your domain classes are the fabric.
The pattern statement matters. Bringing the pieces together, the definition repeated several times in the lecture is:
- A description of communicating objects and classes that is customised to solve a general design problem in a particular context.
- Described with intent, motivation, structure in terms of classes, participants and collaborations (what each role does, and how they talk, in what order).
- Classified by purpose — creational, structural, behavioral — and by scope — whether it applies mainly to classes (inheritance-based, compile-time) or to objects (composition-based, run-time).
- Written at a higher level of abstraction than a hash table or a single data structure, but at a medium granularity where reuse with small changes is realistic.
An adaptation checklist worth using on the exam: (1) rename roles to domain names, (2) map each pattern operation to a domain operation, (3) show which class implements which interface, (4) state one trade-off you accepted by choosing this shape.
Exam note: A common confusion is to treat a pattern as a template you paste. On the exam, expect to argue how you adapted the general structure to the specific problem and why that adaptation preserves the intent. Marks go to the adaptation reasoning, not to a copied diagram.
13.4.3 High-Level Architectures
At the high level are software architectures such as pipes and filters — familiar from Linux command pipelines like cat file | grep pattern | sort — and client-server and layered web architectures. These are course-level topics of their own and need a large amount of work to apply. Patterns are more primitive and smaller than frameworks and architectures, but the same habit of documenting context, problem and solution applies at every level.
Comparison to keep sharp:
| Level | Granularity | What you get | Example |
|---|---|---|---|
| Low — templates, idioms | Fine | Copyable mechanics | for (int i=0; i<n; i++) |
| Medium — design patterns | Medium | Contextual solution shape to adapt | Prototype, Composite, Observer |
| High — architecture | Coarse | System-wide organisation | Pipes-and-filters, MVC, Client-Server |
Patterns are therefore primitive and contextual — smaller than a framework, bigger than a loop, and always tied to a particular design context.
13.4.4 The Full Definition Used in the Session
This section consolidates the scattered phrases from the lecture into one working definition you can quote.
- A description of communicating objects and classes customised to solve a general design problem in a particular context.
- Described with intent, motivation, structure in terms of classes, participants and collaborations.
- Classified by purpose — creational, structural, behavioral — and by scope — class vs. object.
- Written at a medium granularity where reuse with small changes is realistic.
Intuition — Medium means minimal-adaptation reuse. Low-level means you reuse with almost no change; high-level means you reuse the idea but must build much yourself. Medium-level patterns sit where borrowing is most efficient: you reuse the collaboration but rework names, signatures and concrete classes to match your problem in hours, not weeks.
These descriptions rest on object-oriented principles already covered and worth recalling explicitly because they explain why many patterns look alike at the interface level: abstraction, encapsulation, modularity, separation of concerns, coupling and cohesion, divide and conquer, single point of reference, separation of interface from implementation (so you get more reuse), separation of policy from mechanism (so policy changes are easy to handle), and sufficiency plus completeness. If you can name the principle a pattern exploits — for instance, separation of interface from implementation in Adapter and Facade, or divide-and-conquer in Composite — you can explain its leverage more convincingly.
Origins — Christopher Alexander. The father of the pattern idea is presented as Christopher Alexander, a civil engineer and architect who documented a large number of patterns for buildings, bridges and towns. His work A Pattern Language and related books explained patterns as a way to organise the implicit knowledge designers carry about how to solve recurring problems when they build things — courtyards that gather people, light that needs two sides, circulation that avoids crossing. The software community, led by Kent Beck, Ward Cunningham and later the GoF, borrowed the vocabulary deliberately: name the recurring problem, describe the context, give the proven spatial or structural solution, state the consequences. That lineage explains why the pattern habit today appears across industries as a de facto documentation standard.
Q & A — Two granularities discussions from the session.
Q: Where have you used iterators? What does an iterator give you? A: An iterator — a small object you create over a collection that provides a uniform way to step through elements without exposing the underlying structure — is a classic medium-level behavioural pattern. In Java you have a collection interface (Iterable) and iterator implementations over linked lists, priority queues, array lists and other collections. You obtain an iterator and call hasNext() / next() uniformly. Copy constructors are sometimes mentioned in the same breath, but they are a different mechanism (a construction idiom for copying values); the idea that stays the same is that the structure and the intent remain even if the language mechanism varies. You do not expose whether storage is an array or a linked list — the iterator hides that representation.
Q: How does a pattern differ from a template or a framework? A: Templates in C++ are fine-grained generic solutions — for example, vector<T> — you use with minor changes by supplying a type argument. Frameworks are larger and invert control for a domain — they call your code at extension points (Hollywood principle: "don't call us, we'll call you"). Design patterns sit between them — medium-level, contextual solutions you adapt rather than copy directly or inhabit. Pipes-and-filters is larger still — an architecture. You choose based on recurrence, generality and how much customisation the context needs.
Visual — picture a ruler from "copy directly" on the left to "invent a system" on the right. Templates sit near the left tick, patterns in the middle where you still trace the tissue-paper outline, frameworks well to the right where you fill in the Hollywood callbacks, architectures at the far right where you lay out the whole street plan.
Recap — Granularity. Low-level constructs are copied, architectures are invented, patterns are adapted. The working definition is a customised description of communicating objects and classes, classified by purpose (creational / structural / behavioral) and scope (class / object). The idea travels from Alexander's built environment into software via the same documentation habit, and it rests on the same OO principles — separation of concerns, separation of interface from implementation, and protected variation — that make adaptation useful and local.
13.5 The Three Families of GoF Patterns
13.5.1 Purpose-Based Classification
The GoF catalogue groups its 23 patterns into three families by purpose — what design force they relieve. This grouping is the first filter you should apply when facing a problem.
- Creational patterns — how you create and arrange objects, giving the designer more flexibility than direct, static construction with
new. They separate the operation of creation from the rest of the application so creation is not tightly bound to one class and so a family of related products can be produced consistently. Constructors are the standard way to create objects, and these patterns add controlled variations and global access points. Examples discussed in detail in this session are Prototype (clone a prototypical instance) and Singleton (one instance, global access). Other members named and widely used are Factory Method, Abstract Factory and Builder. The creational theme is: who creates what, when, and how the concrete class is hidden behind an interface.
- Structural patterns — how you arrange classes and objects into larger structures, using abstraction, aggregation, inheritance and composition to shape the static make-up of a system. They answer "how do parts compose into a whole that still behaves like a part, or how do I make two mismatched interfaces work together?" The detailed example in this session is Composite; Adapter and Facade later in the lecture are also structural. The structural theme is: how the skeleton fits together and how it protects clients from change.
- Behavioral patterns — how objects take on responsibilities and communicate, especially how you assign and distribute behavior across objects and how you describe collaborations and message flows at run-time. Observer, also called publish-subscribe and earlier known in Smalltalk and MVC frameworks, is the detailed behavioral example here. Other behavioral forms mentioned include Iterator, Strategy, Command and State. The behavioral theme is: who does what, when, and how a change or request propagates.
Quick filter — Which shelf to search?
| Symptom you observe | Ask | Likely family | Check first |
|---|---|---|---|
| Object creation is rigid, always tied to one class, or the number of objects is not known until runtime | Is the problem how objects appear? | Creational | Prototype (clone), Singleton (one instance), Factory/Abstract Factory (family) |
| A group should be usable exactly like a single item, or two interfaces mismatch but do similar things | Is the problem how parts fit? | Structural | Composite (part-whole uniform), Adapter (interface translation), Facade (simple front over complex subsystems) |
| A change in one place should flow to many places automatically, or responsibilities should be distributed without tight coupling | Is the problem how behavior flows? | Behavioral | Observer (one-to-many notification), Iterator (uniform traversal), Strategy (pluggable policy) |
If you classify before you sketch, you avoid trying every diagram at random.
Real-world note from the session: the GoF purpose groups act as a rapid triage when you face a problem on the whiteboard. That triage is itself a pattern habit — name the family, then narrow to candidates, then compare intents.
13.5.2 Scope and Perspectives
Alongside purpose, patterns are also seen as class-scope or object-scope depending on whether the variation is achieved mainly through inheritance at class level (compile-time, hierarchy-driven) or through object composition at run-time (delegation, aggregation, lists of peers). Classical examples: Factory Method in its class form uses inheritance to let subclasses decide what to create; Prototype, Composite and Observer lean on object composition to vary behaviour at run-time by configuring objects. In practice most patterns involve both — Composite inherits from the common interface and aggregates children; Adapter inherits from the target and holds the adaptee — but the dominant variation mechanism determines the scope label.
At a higher level, the lecture stresses that any object-oriented solution is at its core a collaboration or communication among objects in a particular context, and that context is always important for choosing and explaining a pattern. That is why two patterns can share a similar structure (for instance, Adapter and Bridge both place an indirection between an abstraction and an implementation) yet differ fundamentally in intent and when they are applied: Adapter repairs an existing mismatch after the fact, Bridge is designed up-front to let abstraction and implementation vary independently.
Pitfall — Class vs. object scope confusion. Students often label a pattern as "class-based" simply because it has a class diagram. The test is not whether classes exist but where variation happens: if you vary by subclassing a creator, the pattern is class-scoped; if you vary by plugging different delegate objects into a composite or subject at run-time, it is object-scoped. Use that test when the exam asks you to discuss scope.
Q: Can you apply a single pattern to an exam question? A: Rarely. Most exam problems in this course need several patterns together, and marks are allocated for each correct identification plus the argument that the combination covers the needs. Practise placing two to three patterns into one design — for instance, Singleton for the factory that returns Adapters, Facade over a subsystem that internally uses Strategy for pluggable rules — rather than hunting for a single perfect pattern. The lecture frames combination-and-justification as the core exam skill.
Visual: imagine three shelves labeled CREATIONAL, STRUCTURAL, BEHAVIORAL, each holding 5–8 catalogue sheets, and a side label on each sheet marking C (class) or O (object). When a symptom appears, you walk first to the right shelf, then pull two sheets and compare their intents before you draw.
Recap — Two classification axes. By purpose the 23 GoF patterns fall into creational (flexible creation), structural (composed skeletons) and behavioral (distributed responsibilities and communication). By scope they are class (inheritance, compile-time) or object (composition, run-time). Purpose is the primary search filter; scope explains where variation lives. Any OO solution, under this view, is objects collaborating in context — which is why context must appear in your exam justification, not just the diagram.
13.6 Prototype — Cloning Objects from a Model
13.6.1 The Problem
Hook — The canvas that never knows how many copies it will need. Open a graphical editor — Word, PowerPoint, Visio, or any UML drawing tool. You place a blue rounded rectangle on the canvas, format it, then realise your diagram needs 40 of them at different sizes. Building each from scratch is slow. What you really want is to point at the one you already made, say "give me another just like this," and then tweak its size.
A very common need appears in graphical editors, word processors, presentation tools and UML drawing tools: a user works on a canvas and wants to make a copy of an existing graphical item — a shape, a text box, a connector — and then change its size or position. The user does not want to build the new item from nothing each time. The system needs to produce clones of a prototype item quickly, without knowing in advance how many copies will be needed.
Put abstractly: there is an original item, you need to make a clone, and the user should be able to define properties such as the size of the clone. You cannot pre-create a fixed number of objects because you do not know the number in advance. A creation approach that hard-codes new Circle() for every new shape with a long conditional chain (if shapeType == "circle" then new Circle() else if shapeType == "rectangle" ...) is brittle and forces the client to know every concrete class.
The same requirement appears in less visual domains: cloning configured report templates, cells in a spreadsheet engine, or prototype game entities where the base attributes are tuned once and then copied.
13.6.2 The Solution Shape
Purpose — Create by copying, not by constructing. Prototype solves this by keeping a model object (the prototype) and cloning it. The client interacts only with a common abstraction, never with concrete classes directly. One prototype per kind is enough; clones are created on demand rather than storing a large pool.
- A common interface or abstract class, often called Shape, defines the operations that all shapes share —
clone(),draw(), perhapsgetArea(). - Concrete classes such as Circle and Rectangle implement Shape and hold their own specific data such as radius for a circle or width/height for a rectangle.
- A client interacts only with the Shape interface, not with each concrete class directly. The relationship between the client and the concrete classes passes through inheritance from Shape — this is where interface separation protects the client.
- A Shape cache or registry (often a
Map<String, Shape>orDictionary) can hold one prototypical instance of each kind, keyed by name. When a new item is needed, the client asks the prototype to clone itself and then adjusts the new copy, for example by supplying a new radius.
The class diagram described in the lecture shows Shape at the top as an interface, the concrete shapes below it inheriting from it, and the client using Shape via aggregation to the registry and via a clone() call. The key move is that only one prototype per kind needs to exist; clones are created on demand.
Pseudocode for the idea, in the spirit of the session and matching the standard textbook example:
interface Shape {
Shape clone();
void draw();
}
class Circle implements Shape {
private int radius;
Circle(int r) { radius = r; }
Shape clone() { return new Circle(this.radius); }
void draw() { /* draw with radius */ }
}
class Rectangle implements Shape {
private int width, height;
Rectangle(int w, int h) { width = w; height = h; }
Shape clone() { return new Rectangle(this.width, this.height); }
void draw() { /* draw rectangle */ }
}
// registry
Map<String, Shape> prototypes = new HashMap<>();
prototypes.put("circle", new Circle(10));
prototypes.put("rectangle", new Rectangle(5, 8));
// client — no knowledge of Circle vs Rectangle
Shape newCircle = prototypes.get("circle").clone();
Real-world note from the lecture: the same Shape idea is the standard example found on the web for Prototype. Search "prototype pattern Shape Circle Rectangle shape cache" and you will find Shape, Circle and Rectangle with a shape cache and a clone operation — runnable code you can extend to new shapes with almost no client change.
Worked trace — Cloning with real numbers.
Setup: registry holds Circle(radius=10) under key "circle" and Rectangle(5,8) under "rectangle".
- Client requests
"circle"clone:Shape c1 = prototypes.get("circle").clone();→c1.radius = 10(copy). - Client tweaks the clone:
((Circle)c1).setRadius(15);— or, in a cleaner design, calls a parameterised clone-style factory: the clone starts at 10, then setter changes it to 15. Original prototype stays at 10. - Client requests second circle:
Shape c2 = prototypes.get("circle").clone();→c2.radius = 10again — proves prototype was not mutated. - Client draws both:
c1.draw()draws radius 15,c2.draw()draws radius 10,prototypes.get("rectangle").clone().draw()draws 5×8. - Add a new shape: add
Triangleclass implementingShape, putnew Triangle(3,4,5)as prototype — no client code changes except the registry population.
Sense-check: we created two independent circles from one prototype without the client ever writing new Circle. If the registry had stored 100 pre-made circles instead, adding Triangle would require editing every creation site — the clone approach avoids that.
Assumptions & Scope — When Prototype fits and when it does not.
Use it when: the number of objects of a given kind is not known until runtime and the user controls the count; the cost of creation via new is significant or you want to avoid a long conditional creation chain; the concrete class to instantiate is not known to the client and should stay hidden behind an interface.
Do not force it when: object creation is trivial and not variant (a plain new is clearer); deep copying is required and object graphs contain cycles or shared references that make correct cloning hard; you need parameterised construction with many different arguments — Factory or Builder may be clearer there.
What can go wrong: Shallow copy pitfalls — if a Shape holds a reference to a mutable Style object and clone() copies only the reference, the original and the clone share style and interfere. Decide explicitly between shallow and deep copy and document it. In Java, overriding Object.clone() correctly and handling CloneNotSupportedException adds ceremony; a custom copy() or copy-constructor may be simpler.
Visual to keep: picture a rubber stamp tray (the registry) holding one master stamp per shape — a circle stamp inked at radius 10, a rectangle stamp at 5×8. The client presses the stamp onto paper to make a fresh copy, then trims the copy to size. The master stamp never moves and never runs out; you make as many copies as you need. That is different from a tray that holds 100 pre-stamped copies — the master-tray scales.
13.6.3 Tagging Interfaces and Java Clone
In Java the cloning support touches a well-known interview topic.
- An interface — a type that lists operations without giving their implementation — can be empty of methods and still be useful.
- A tagging interface (also called a marker interface) — an interface with no methods that marks a class as having a property — is that empty form.
Cloneable is the example given of a tagging interface. It has no methods. You implement it to tag a class as one that allows cloning, and the environment then knows it may call the clone mechanism. The language provides a clone() method of protected type in Object that is reachable through this tagging permission. This can feel strange: why implement an interface with no methods? The answer offered is that you want to signal to the runtime that this class is intended to be cloned, even though you do not want to force a public clone implementation on every tagged class. It is a way to declare capability without prescribing the method signature — a permission flag rather than a contract.
Pitfall — Tagging vs. cloning confusion. Implementing Cloneable alone does not give you a public clone() — you must still override clone() (usually calling super.clone()) and handle the protected visibility and the checked exception. Forgetting to do so yields a runtime CloneNotSupportedException even though the class "implements Cloneable." Many teams therefore prefer a copy constructor or a custom copy() method that avoids the ceremony. On the exam, name the tagging role explicitly and do not claim Cloneable adds a clone method — it adds permission.
Q & A — Two discussions from the lecture, deduplicated.
Q: What do you mean by a tagging interface? Do you know an example in Java? A: A tagging interface has no methods at all. You implement it so the environment knows your class has that property. Cloneable is the classic example: it tags a class as cloneable, and the clone method, which lives in Object as protected, can then be used via the tag. The same pattern appears with Serializable. Knowing this definition and this example is considered a challenging interview question because it tests whether you distinguish type from method contract.
Q: Can I just use a copy constructor instead of clone? A: Yes — a copy constructor (e.g., Circle(Circle other) { this.radius = other.radius; }) achieves a similar effect by creating a new object and copying fields from an existing one. The pattern intent stays the same: keep the structure where the client depends on the common Shape interface and clones through it rather than calling concrete constructors directly. The language mechanism may vary — clone(), copy constructor, or a factory method that delegates to a copy constructor — but the collaboration (client → Shape → clone → new instance of concrete type) and the location of the cloning capability remain the same. On the exam, state the mechanism you chose and argue that the intent is preserved.
13.6.4 Using the Pattern Well
Prototype belongs to the creational family because it controls how new instances are created — by copying rather than by direct construction via new with concrete class knowledge. It comes with clear trade-offs.
Gains: hides the concrete class from the client; avoids a long if-else or switch creation chain; allows adding new products (new shape kinds) by adding a prototype, not by editing clients; can be more efficient when object creation is expensive and a cached prototype is cheaper to clone.
Costs: you must manage copying correctly, especially for fields that reference other objects (shallow vs. deep); the set of prototypes must be initialised and kept consistent; explaining cloning permission in Java adds conceptual overhead.
The lecture advises trying the Shape example with code concretely: load prototypes into a Map<String, Shape>, clone from the map, print the clone's radius to verify independence, and extend to a new shape to feel how little the client changes. Beyond graphics canvases, the Swing library example — creating many similar widgets by cloning a configured prototype — is cited as a way to meet Prototype alongside other patterns inside one real codebase.
Exam note: Prototype is a frequent illustration for creation flexibility. If the problem statement says the number of objects of a given kind is not known in advance and the user controls the count at runtime, or that creation should hide the concrete class from the client, consider Prototype. Be ready to show the Shape interface, two concrete shapes, the cache that holds the models, and a client that calls clone() through the interface. Mention shallow vs. deep copy and the tagging interface Cloneable if the question is Java-specific.
Recap — Prototype. Problem: need many variant copies without knowing the count up front. Solution: keep one model per kind and clone through a common interface held in a registry. Mechanism in Java touches the tagging interface Cloneable; alternatives such as copy constructors preserve the same intent. Watch copying semantics and prototype lifecycle.
13.7 Singleton — One Instance with Global Access
13.7.1 The Problem
Hook — The one object everyone must share. Imagine a company system where every screen shows the company name, logo and tax identity. If every module created its own Company object, two screens could show different names after an update. Imagine also a random-number generator seeded once at startup and reused by every student simulation in the lab. Creating a fresh generator per call would break seeding and reproducibility. You need one shared instance, reachable everywhere, without scattering fragile global variables.
Some resources should exist only once for the whole application and many parts of the system should be able to reach that same object. Two small but telling examples given in the lecture are:
- a company name object that holds the organisation identity and is used throughout the company system — changing it should be visible everywhere at once, and
- a random number generator that the environment creates once and all students or classes reuse, often because it is seeded once.
Without a disciplined way to enforce a single instance, code can create many copies by mistake, each with its own state, or reach the resource through messy global variables and bare static fields, which C++ and similar languages try to avoid because they obscure ownership and lifecycle. A hidden second instance is a classic bug: two Company objects diverge, or two generators produce different sequences and break tests.
13.7.2 The Solution Shape
Purpose — One instance, controlled creation, global access. Singleton creates a global access point while guaranteeing there is only one instance. It shifts control of creation from the many clients to the class itself.
- The constructor of the class is made private so outside code cannot call
new Singleton()directly. This is the enforcement mechanism. - The class holds its one instance in a
private staticfield and creates it at the point of declaration (eager) or on first use (lazy). - A public static method, conventionally called
getInstance(), returns that same instance to any caller. Because the method is static and class-scoped, no existing instance is needed to reach it.
Structure in words: a class Singleton with a private constructor, a private static Singleton instance, and a public static Singleton getInstance() { if (instance==null) instance=new Singleton(); return instance; }. Clients call Singleton.getInstance() rather than using new. The instance is created there itself inside the class, and the public method hands it out.
class Singleton {
private static Singleton instance;
private Singleton() { /* private — no outside new */ }
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
public void doWork() { /* shared behaviour */ }
}
// client
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
// s1 == s2 is true — same object
This pattern is the classic interview answer for "where would you use a private constructor?" By default many constructors are package-visible or public; singleton is the deliberate, justified case where you restrict construction to enforce singularity. The combination — private constructor plus public static accessor — is its signature.
A quick check with real references sharpens the picture. Under the lecture's Company example:
Company c1 = Company.getInstance();
c1.setName("Acme Ltd");
Company c2 = Company.getInstance();
System.out.println(c2.getName()); // prints "Acme Ltd" — same instance
If a second new Company() were possible, c2 could hold a different name and the system would be inconsistent. The private constructor prevents that second new.
Worked mini-trace — Random generator singleton.
Goal: all students share one seeded generator.
- First caller:
RandomGen r1 = RandomGen.getInstance();→instanceisnull, so the method createsnew RandomGen(seed=42)and stores it in the static field, returns it. - First use:
r1.nextInt()→ 81 (deterministic from seed 42). - Second caller elsewhere:
RandomGen r2 = RandomGen.getInstance();→instancealready exists, no new creation, returns the same object. - Second use:
r2.nextInt()→ 14 — the sequence continues from wherer1left off, rather than restarting, provingr1 == r2. - Any attempt to write
new RandomGen()outside the class fails at compile time — private constructor blocks it.
Sense-check: if the generator were not singleton, two instances seeded at different times would produce diverging sequences and break reproducible experiments.
13.7.3 When to Use It and What to Watch
Use singleton when you need global reach and a single copy — many clients must share state or a heavy resource, and you want to control creation rather than leaving it to scattered new calls. Classic collaborators: configuration holders, logging services, thread pools, and shared generators seeded once.
The same property that makes singleton useful also makes it risky. Global reach increases coupling and can hide dependencies. A class that calls Singleton.getInstance() deep inside a method hides the fact that it depends on shared mutable state — tests must then reset that state between runs or become order-dependent. Much later design guidance therefore warns to use singleton sparingly and to keep the shared state easy to test, to reset, and if possible immutable after initialisation.
Implementation notes worth remembering:
- Lazy vs. eager: The lecture sketch uses lazy creation (
if null then create). Eager creation (private static Singleton instance = new Singleton();) is simpler and avoids a null check but pays creation cost even if never used. - Thread safety: In a multithreaded environment, two threads calling
getInstance()simultaneously can create two instances if the check is not synchronised. Production code uses synchronisation, double-checked locking, or the enum-singleton idiom in Java — detail not required on the exam but worth noting as a pitfall. - Subclassing limitation: A private constructor makes subclassing difficult. If you anticipate variation in the singleton's behaviour, instance-side methods on the singleton instance are more flexible than static-only helpers, as discussed in the textbook.
Visual: picture a single reception desk in a large office building. Every department that needs the company stamp must walk to that one desk (getInstance()), rather than keeping its own stamp. The desk guarantees there is one stamp and controls who uses it — convenient, but every department now depends on that desk being open and consistent.
Pitfalls — How singleton hurts if overused.
- Hidden coupling. Callers do not declare the dependency in constructors or parameters, so a reader cannot see it from the interface.
- Test pollution. Shared mutable state leaks between tests unless you add a reset hook — which itself weakens the "one instance" guarantee.
- Global mutable state. A singleton that holds changing configuration becomes a global variable by another name; prefer immutability or dependency injection where possible.
A useful exam reflex: if the problem says "only one instance may exist," propose singleton but also state one drawback and one mitigation (e.g., "we accept global access for the configuration store and mitigate test coupling by providing a package-private resetForTesting()").
Q: Why make the constructor private? Is that not unusual? A: It is unusual and deliberately so. Making the constructor private stops anyone outside the class from creating extra instances. Only the class itself can create the one allowed instance, and it then offers a controlled public static method to hand that instance out. That private constructor plus public static accessor is the structural signature of singleton — recognisable at a glance and often tested at interview.
Exam note: If a problem says an object must be shared by all parts of the system and must be created only once — configuration, registry, shared generator — singleton is a strong candidate. Draw the private constructor and the static getInstance() method, name the private static instance field, and explain how encapsulation of creation is achieved. Also state one consequence (global coupling / test difficulty) to show you know the trade-off.
Recap — Singleton. Problem: one shared resource with global reach. Solution: private constructor + private static instance + public static getInstance(). Gain: single controlled point of creation and access. Cost: global coupling and testing friction. Use where singularity is truly required, not as a default for every shared object.
13.8 Composite — Treating a Single Object and a Group the Same Way
13.8.1 The Problem
Hook — One price tag for one pen and for a box of pens. You sell a single pen with its own price. You also sell a bundle of 12 pens where you join the properties of each single pen into a new item and sell the bundle as one thing with one price and one barcode. The shop's cashier should call getPrice() and play() (or scan()) in the same way whether they are holding one pen or a whole bundle. How do you build a whole that can be used exactly like one of its parts?
How do you build a whole that can be used exactly like one of its parts? Selling pens is the simple analogy used in the lecture: a single pen with its own price and details, and a bundle of pens where you join the properties of each single pen into a new sellable whole. The buyer treats the bundle as a single item while it still contains many individual items.
The session gives a media example that carries the same structure: there are individual audio clips and video clips that can be played, and there is a sequence that holds a collection of those clips. The user wants to play() a single clip or play() the whole sequence through the same operation — the sequence should play each of its clips in turn and getPrice() should aggregate prices. A plain list fails here: a list does not share the clip interface and cannot be passed where a single clip is expected.
In an earlier case study referred to as the gate example, the same part-whole tension had appeared — individual gates versus assemblies of gates that still behave like a gate.
The part-whole requirement. A part-whole hierarchy where the whole must support the same operations as the part: play a single clip or a whole sequence, price a single pen or a bundle. The client should not ask "is this a single or a group?" before acting.
13.8.2 The Solution Shape
Purpose — Uniform treatment via one interface. Composite builds its solution from two ideas used together: inheritance (for uniform type) and aggregation (for containment). The composite looks like a leaf from the outside and holds leaves on the inside.
- A common Component interface, often called MediaClip or Component, declares shared operations such as
play()andgetPrice(), plus child-management operations such asadd(MediaClip m)andremove(...)(sometimes only on the composite). - Leaf classes such as VideoClip and SoundClip (or SinglePen) implement the interface for single items — each leaf keeps its own independent identity and lifecycle and its own implementation of
play()andgetPrice(). - A Composite class such as Sequence or AddSequence also implements the same interface. Inside it holds a collection of MediaClip by aggregation — each leaf keeps independent identity — and its own implementation of
play()simply iterates and callsplay()on each member. Similarly,getPrice()sums or combines values returned by members.
The class diagram discussed shows MediaClip at the top, VideoClip and SoundClip as leaves, and Sequence as the composite that both inherits from MediaClip and aggregates MediaClip (a diamond-linked collection). Common methods are implemented by leaves and by the composite with forwarding.
Why both inheritance and aggregation together, not just one?
- Inheritance lets the composite be used wherever a single clip is expected — it is a kind of clip from the outside, so a variable typed
MediaClipcan hold either. This is the uniformity guarantee. - Aggregation lets the composite hold many clips without copying identity or lifecycle — the clips exist independently and can be moved between composites.
- Dynamic binding then does the rest: when the client calls
play()on aMediaClipreference, the run-time picks the leaf version for a single item and the composite version for a group, and the composite forwards the call to its members.
Pseudocode for the intent, elaborated with prices to make forwarding visible:
interface MediaClip { void play(); int getPrice(); }
class SoundClip implements MediaClip {
void play() { /* play sound: drum beat */ }
int getPrice() { return 5; }
}
class VideoClip implements MediaClip {
void play() { /* play video: 2-sec animation */ }
int getPrice() { return 10; }
}
class Sequence implements MediaClip {
List<MediaClip> parts = new ArrayList<>();
void add(MediaClip m) { parts.add(m); }
void remove(MediaClip m) { parts.remove(m); }
void play() { for (MediaClip m : parts) m.play(); }
int getPrice() { int s = 0; for (MediaClip m : parts) s += m.getPrice(); return s; }
}
// client — polymorphic usage
MediaClip single = new SoundClip();
single.play(); // plays one sound, price 5
Sequence seq = new Sequence();
seq.add(new SoundClip()); // price 5
seq.add(new VideoClip()); // price 10
seq.add(seq2); // composites can nest — a sequence of sequences
MediaClip whole = seq; // upcast — same type
whole.play(); // forwards to all three internal members
int price = whole.getPrice(); // 15 — aggregated
A client can then write MediaClip c = new SoundClip(); c.play(); and also Sequence s = new Sequence(); s.add(new SoundClip()); s.add(new VideoClip()); s.play(); with no change in the call site. A colour-coded version of this diagram was shown: interface operations in one colour, forwarding/aggregation management in another — worth recreating mentally to separate what the component promises from how the composite fulfils it.
Worked trace — Sequence play and price with real numbers.
Build: SoundClip s1(price 5), VideoClip v1(price 10), nested Sequence inner containing SoundClip s2(price 5).
Sequence outer = new Sequence();outer.add(s1);→parts = [s1], outer price so far 5.outer.add(v1);→parts = [s1, v1], outer price 15.Sequence inner = new Sequence(); inner.add(s2);→ inner price 5.outer.add(inner);→parts = [s1, v1, inner], outer price = 5 + 10 + 5 = 20.outer.play();→ callss1.play()→ drum beat;v1.play()→ animation;inner.play()→ which itself callss2.play()→ second drum beat. Order is the insertion order.- Type check:
outer instanceof MediaClipis true — the composite is a MediaClip.
Sense-check: adding a new leaf type (e.g., ImageClip) requires only making it implement MediaClip; no change to Sequence or to client forwarding logic — the composite is open for extension.
Assumptions & Scope — When Composite fits.
Use it when: you have a part-whole hierarchy and want clients to ignore the difference between a single object and a composition; operations should apply recursively (draw, play, price, save); the structure is tree-like and nesting is natural (files/folders, scene graphs, price bundles).
Do not force it when: the whole does not meaningfully share the part's operations (a bundle that cannot be "played" should not pretend to be a clip); or when the hierarchy has strong type constraints that would be violated by treating everything uniformly — in those cases a plain collection with explicit iteration is clearer.
Aggregation vs. composition note: The lecture models containment as aggregation — leaves keep independent identity and lifecycle outside the composite. If leaves are owned exclusively and die with the composite, model it as composition instead. Both satisfy the uniform-interface goal, but lifecycle expectations differ and should be stated in your diagram.
Visual: picture a music playlist app. A Song is a leaf — you can press play on one song. A Playlist is a composite — you can also press play on a playlist, which just plays each song inside in order. A playlist of playlists is still a playlist. The same play button works no matter how deep the nesting.
Pitfalls — How students misstate Composite.
- Calling a list a composite. A
List<MediaClip>alone fails the test: it does not implementMediaClipand cannot be passed where a single clip is expected. Composite requires the inheritance link plus forwarding. - Forgetting forwarding. Declaring that
Sequence implements MediaClipwithout implementingplay()as a loop overpartsleaves an empty composite — not a composite. - Mixing identity. Treating aggregation as simple value-copying: if leaves are copied on add, changes to the original pen do not reflect in the bundle. State whether you share or copy and justify it.
13.8.3 Why This Matters and How It Is Tested
Composite is a structural pattern because it shapes how objects are arranged into a recursive tree. The key property to remember and to state in an answer is: leaves have their own identity and can be used alone, yet the composite can be used as if it were a single leaf. That sentence alone signals that you understand the two mechanisms together.
The lecture frames it as a structural pattern alongside inheritance-plus-aggregation questions long used in interviews: "When would you use inheritance and aggregation together?" Composite is the canonical answer. It also connects naturally to other patterns: Iterator often traverses the composite's parts, Decorator wraps a component while Composite groups them, and Composite is itself the shape behind pricing strategies that combine multiple ISalePricingStrategy objects behind one getTotal() call in the textbook example.
Real-world settings worth naming:
- File systems where a Folder may hold Files or other Folders —
open(),getSize()work uniformly. - Graphical scenes where a Picture holds Shapes (or
JComponent/Containerin Swing) —draw()orpaintComponent()works uniformly. - Price bundles in retail where
SinglePen(price 5)vsPenBundle(price 5+10+...)sharegetPrice(). - The textbook's pricing composite where a
CompositePricingStrategyholds a list of pricing strategies and returns the best total.
Q & A — Two clarifications from the session.
Q: Is a composite not just a list of clips? A: No. A plain list would not share the same interface as a single clip and would not let you call play() in the same way on both. Composite adds the inheritance link so the group implements the same interface as its members, plus an aggregation link to hold them, plus forwarding of shared operations. That triple — interface, aggregation, forwarding — is what makes a group usable as a single item. A list alone gives you aggregation only.
Q: Where would you place compile-time versus run-time choices here? A: The interface and inheritance give the compile-time shape — the type system guarantees a MediaClip variable can hold either a leaf or a composite. The aggregation collection and the forwarding loop are the run-time behaviour — at execution you choose whether the reference points to a leaf or to a nested composite, and dynamic binding picks the right play(). This split is typical of object-level composite.
Exam note: When a problem describes a part-whole hierarchy where a whole should support the same operations as a part — play a single clip or a whole sequence, price a single pen or a bundle — Composite is the expected answer. Draw the common interface at the top, two or more leaves, and a composite that both implements the interface and holds a List<Component>, with forwarding of play()/getPrice(). State that inheritance gives uniform type and aggregation gives containment, and name one non-trivial consequence (e.g., too-general type safety vs. uniformity trade-off).
Recap — Composite. Problem: whole must behave like part. Solution: one common interface + leaves + composite that implements the interface and aggregates components, forwarding operations recursively. Mechanism: inheritance for uniformity, aggregation for containment, dynamic binding for dispatch. Related to iterator, decorator and pricing composites.
13.9 Observer — When One Change Must Flow to Many Views
13.9.1 The Problem
Hook — One change, many views, no tangled wires. Imagine a sales spreadsheet whose single data table feeds three views at once — a pie chart, a histogram and a scatter plot — each with its own controller C1, C2, C3. A manager edits a number in the table, or drags a slice in the pie. Every other view must show the new numbers automatically, yet you do not want each view to know the internals of every other view, or to poll for changes. How do you keep many views in step with one source of truth without tying them tightly together?
How do you keep many views in step with one underlying piece of data, without tying them tightly together? The original home of this pattern is Smalltalk, described in the lecture as the purest form of object-oriented programming, where the early framework called Model-View-Controller (MVC) — now widely seen as a framework built on the Observer idea — was first explored.
Picture a data table that is the model. Several views show that same model in different ways: view one is a pie chart, view two is a histogram, view three is a scatter or bar chart. Each view has its own controller, labelled C1, C2, C3 and so on. A user may change the model through any view — for example by editing numbers in a table or dragging a slice in the pie chart — and all views must automatically show the new state. You do not want each view to know the internals of every other view, and you want the set of views to be open — you may add a new view tomorrow without editing the model.
Another picture suggested in the lecture is a blackboard that is the observable item and several students who are observers, labelled 01, 02, 03, 04. The students all look at the same board. When the board changes through any student, all students are notified. The channel-subscription metaphor is also used: many subscribers watch a channel and receive a notification when the channel updates.
The same need shows up in many places quoted in the lecture: building a directory browser where the same data can be shown in a file-explorer view or a command-prompt view, and in graphical user interfaces where pressing a button should cause several registered listeners to react — without the button knowing who they are.
The one-to-many dependency. The core dependency is one-to-many: one subject (the model, the blackboard, the channel) and many observers (views, students, subscribers). When the subject changes, it notifies; observers then bring themselves up to date. The subject does not know the concrete class of any observer — only that observers implement a common interface. That ignorance is the decoupling.
13.9.2 The Solution Shape
Purpose — Separation of data from its presentations, linked by notification. Observer separates the data from its presentations and links them with a one-to-many dependency that works by broadcast notification. It is the textbook example of favouring loose coupling over direct references.
Roles and operations as presented in the lecture:
- Subject — holds the application state and the list of observers. It offers operations
attach(Observer o),detach(Observer o)andnotifyObservers(). In Java the library provides a classObservablethat keeps this list and methods to add, delete and notify, plus a flag such ashasChanged()that is tested before notification to avoid redundant redraws. The concrete subject (e.g.,StaffTable,DataModel,ConcreteSubject) holds the actual model data and agetState()accessor. - Observer interface — declares an
update()operation. Concrete observers such asPieChart,Histogram,BarChartor a button'sActionListenerimplement this interface and, when notified, fetch the new state from the subject and redraw or refresh themselves. In each concrete observer the update logic is named to fit the application, but the shape is the same.
The interaction sketched in the lecture is:
- An observer registers with the subject through
attach. - Any observer (or external actor) may request a state change on the subject through
setStateor similar. - The subject marks that it has changed (
setChanged/hasChanged) and callsnotify()on its list. - Each observer calls
getState()on the subject and then updates/redraws.
Pseudocode for the intent — interface version matching the lecture, plus a note on the Java built-in:
interface Observer { void update(); }
interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private Data state;
private boolean changed = false;
void setState(Data d) { state = d; changed = true; notifyObservers(); }
Data getState() { return state; }
void attach(Observer o) { observers.add(o); }
void detach(Observer o) { observers.remove(o); }
void notifyObservers() {
if (!changed) return;
for (Observer o : observers) o.update();
changed = false;
}
}
class PieChart implements Observer {
private ConcreteSubject subject;
PieChart(ConcreteSubject s) { subject = s; s.attach(this); }
void update() { Data d = subject.getState(); // redraw pie with d
}
}
class Histogram implements Observer {
private ConcreteSubject subject;
void update() { Data d = subject.getState(); // redraw bars with d
}
}
In the Java library variant described, Observable is a class (not an interface) and Observer is an interface with update(Observable o, Object arg). A typical subject then calls setChanged(); notifyObservers(); and each update pulls the new data. A non-Java design often makes both sides interfaces. Either way, the collaboration is the same.
This achieves separation between application data and its presentation. Views stay separate from the data, and both sides remain reusable. The subject does not know the concrete class of any observer; it only knows they implement the observer interface. Communication is by broadcast notification, and there is no tight type coupling between subject and concrete observers.
Worked trace — Table edits flow to pie and histogram.
Start: ConcreteSubject table holds Data = [A:30, B:40, C:30]. PieChart pie and Histogram hist have attached.
- User drags pie slice A from 30 → 50 via pie's controller C1. Pie calls
table.setState([A:50, B:40, C:30])and markschanged = true. table.notifyObservers()iterates:pie.update()→piepulls[A:50,B:40,C:30]and redraws larger slice A.hist.update()→histpulls the same data and redraws taller bar A.- A third observer
ScatterViewadded later viatable.attach(new ScatterView(table))— no change totablecode — will also receive the next notification. - If
table.setState()is called with identical data andhasChangedremains false,notifyObservers()returns without looping — redundant redraw is avoided.
Sense-check: adding a fourth view (TableView) costs one attach call; removing the pie costs one detach. The subject's code never mentions PieChart or Histogram by name.
Assumptions & Scope — When Observer fits.
Use it when: two aspects — data and its views — must stay separate and both be reusable; one object depends on another with a one-to-many dependency (one subject, many observers); a change in one place should be reflected automatically in others without the subject knowing who its observers are; the set of observers is open-ended and not known in advance.
Watch out when: notification order matters to correctness (observers should be independent); observers trigger further subject changes that cause notification cycles (guard with hasChanged or batching); update granularity is too coarse and every tiny change forces full redraws — consider push vs. pull detail or event objects carrying delta.
Push vs. pull note: The lecture shows a pull form where observers call getState(). A push variant sends the changed data as an argument to update(data). Pull keeps the subject simpler; push avoids a second round-trip. Name which you use in your answer.
Visual: picture a radio broadcast tower (the subject) and a ring of houses with radios (observers). The tower does not know who owns a radio — it just broadcasts on a frequency. Any house that tuned in (attach) hears the update. A new house can tune in tomorrow without the tower being rewired.
13.9.3 When to Choose Observer
Choose observer when the textbook checklist is met:
- Two aspects — data and presentation — must stay separate and both be reusable across contexts.
- One subject, many dependents, with a one-to-many dependency that may grow at run-time.
- A change in one place should appear automatically in others, but the subject should not need to know who the others are.
- The set of objects that must be notified is open-ended, not fixed at compile time.
The most memorable property to state on the exam is: a change in the subject automatically shows up in all dependent views without the subject holding knowledge of their concrete types. That automatic update — which the lecture calls simple and almost magical — comes directly from keeping the number of dependents open and avoiding direct knowledge between the sides. It is also why the channel-subscription metaphor fits: subscribing is attaching, publishing is notifying.
Common neighbours to distinguish:
- Mediator also decouples peers, but via a central hub that knows workflow, whereas Observer broadcasts from one source to many dependents without a hub workflow.
- Publish-subscribe channels at larger scale are Observer extended across processes.
Real-world settings beyond charts and tables quoted in the lecture: the event delegation model in graphical interfaces — ActionListener, MouseListener objects register with a JButton, and when the button is pressed every registered listener is notified — and publish-subscribe channels where many subscribers watch a topic. Both are Observer at different scales.
Q & A — Two anchor questions from the lecture.
Q: How does this relate to the earlier MVC idea? A: MVC (Model-View-Controller) is a framework for building programs where the model is the data table, each view displays the model in a different form, and each controller mediates input for its view. Observer is the core notification mechanism inside that framework: the model acts as the subject and each view (+ controller pair) acts as an observer. When any controller changes the model, the model notifies all views and each view refreshes — whether the change arrived via the histogram controller or the pie controller does not matter. Because MVC embeds Observer so centrally, newer descriptions often refer to the whole arrangement simply as Observer — the notification idea is the part that generalises beyond one framework. Smalltalk is named as its historical home as the purest OO environment where MVC was born.
Q: Is observer hard to implement? A: Mechanically no — it is a list of observers and a notification loop with attach/detach and a hasChanged guard. The discipline lies in keeping the subject open to new observers (no concrete observer names in the subject), hiding observer details from the subject, and deciding precisely when to mark a change and when to notify so redraws stay correct and not wasteful. That decision — eager notify on every setter vs. batched notify — is the design choice to argue in your answer.
Exam note: Observer is described as very popular and very important. If the problem statement says there are two separate aspects where one depends on the other, a change in one must flow automatically to the others, and the number of dependents is not fixed, draw the Subject with attach, detach and notify (plus hasChanged if you use it) and the Observer with update, then name the concrete subject and concrete observers for the domain at hand (e.g., StaffTable → PieChart, Histogram). State pull vs. push and one trade-off (automatic consistency vs. redundant updates / ordering).
Recap — Observer. Problem: one-to-many consistency without coupling. Solution: Subject maintains an observer list behind an interface and notifies; observers pull or are pushed the new state. Gain: separation of data and views and open-ended dependents. Cost: notification management and ordering. Home: Smalltalk MVC; modern homes: GUI listeners, pub-sub.
13.10 Facade — A Simple Door in Front of a Complex House
13.10.1 The Problem
Hook — Why does one command do so much? You type gcc hello.c on Linux and a complete program appears. Under that one command, a scanner reads characters, a parser builds a tree, a node builder connects hundreds of nodes, and a code generator emits bytecode. Imagine if you had to drive each of those subsystems by hand, in the right order, with the right wiring, for every compile. One wrong connection and the build fails. Facade is the design answer to that explosion of wiring.
How do you hide a complex set of cooperating subsystems behind one easy entry point? The best example offered in the lecture is a compiler. The work inside a compiler is complex and involves many steps and many interacting parts. A client such as a programmer just wants to compile a program by giving a simple command and receiving the output, not by driving each subsystem in the right order.
A second example offered is a product purchase that must apply many rules: credit card handling, debit card handling, coupons, gift certificates, expiry checks and similar business rules. A rule engine needs to evaluate the full set of rules behind the scenes and report success or failure, but the client operation is simply purchase(paymentChoice).
In both cases the underlying subsystems have many dependencies and the risk of circular or wrong ordering is real. If clients talk directly to each subsystem class — holding references to Scanner, Parser, NodeBuilder, CodeGenerator separately — they can create mistakes, duplicate wiring and brittle code, and the program may not build. Worse, every client rebuilds the same wiring slightly differently, so a change in one subsystem ripples to many clients.
The facade situation. Many interacting subsystem classes exist. Clients need a single entry point and a default view that satisfies most use cases. The facade must hide dependency wiring and ordering without taking away the ability of power clients to reach deeper detail if needed.
13.10.2 The Solution Shape
Purpose — One simple front over a complex house. Facade introduces a single entry class that knows how to reach the subsystems and offers a simple default view that satisfies most clients. It is a structural pattern because it shapes the layer sitting in front of the subsystems and controls coupling — not because it adds new domain behaviour.
- Clients talk only to the facade. They send requests to the facade rather than to the individual subsystem classes.
- The facade forwards those requests to the right subsystems, handles the ordering, and hides the dependency wiring. It knows which subsystem needs which data and when.
- Clients have no direct access to the subsystem classes through the facade path. The facade shields them and reduces coupling between subsystems and clients. Any later change inside the subsystems can be handled by changing the facade while most clients keep the same call.
For the compiler example the subsystems named are CodeStream that carries code, a Scanner or lexical analyzer, a Parser that builds a parse tree, a NodeBuilder that holds visual and other nodes, and a CodeGenerator that emits bytecode. Each has its own methods. The Compiler as facade holds references to these subsystems and offers methods that, depending on parameters, create different bundles and drive the correct sequence — for example, scanning then parsing then building then generating. A client then types a single command such as gcc with parameters and the facade does the compilation job and returns the result. The textbook-companion diagram shows each subsystem behind the facade, with the client arrow pointing only to the Compiler box.
For the purchase example the facade is the purchase operation or the POSRuleEngineFacade / rule-engine facade discussed in the textbook companions. Inside it evaluates the set of rules — credit vs. debit, coupon validity, gift-certificate expiry — and decides whether a particular combination is valid (for instance, marking an expired coupon as invalid), while the client sees only purchase(paymentType, coupon) with a payment-choice parameter.
A small pseudocode sketch of the forwarding:
// subsystems
class Scanner { Tokens scan(Source s) { ... } }
class Parser { ParseTree parse(Tokens t) { ... } }
class NodeBuilder { Nodes build(ParseTree p) { ... } }
class CodeGenerator { Bytecode emit(Nodes n) { ... } }
// facade
class Compiler {
private Scanner scanner = new Scanner();
private Parser parser = new Parser();
private NodeBuilder builder = new NodeBuilder();
private CodeGenerator gen = new CodeGenerator();
Bytecode compile(Source s) {
Tokens t = scanner.scan(s);
ParseTree p = parser.parse(t);
Nodes n = builder.build(p);
return gen.emit(n);
}
}
// client — one line instead of four orchestrations
Compiler facade = new Compiler();
Bytecode out = facade.compile(source);
Benefits captured in the lecture:
- Facade hides subsystems from accidental misuse — clients cannot call subsystems out of order.
- It removes a large number of direct dependencies from clients — one dependency on the facade replaces four or five on subsystems.
- It gives a single place to adapt when subsystems change — only the facade is edited.
- It does not force all clients to use the same simple view — power clients that need fine control can still reach more detail if the design allows a layered facade (a simple facade for most, deeper subsystem access for advanced use), but for most clients the simple view is enough.
Placement note: Facade is placed among structural patterns because it shapes the layer sitting in front of the subsystems and governs how classes and objects are connected to reduce coupling, not how responsibilities are distributed over time (which would be behavioral).
Worked forwarding — From gcc to bytecode with real steps.
Source: hello.c containing int main(){return 0;}
- Client calls
compiler.compile(sourceFile)with flag-O2. - Facade
Compilerdecides bundle:scan → parse → build → generate. Scanner.scan()emits token stream[INT, MAIN, LPAREN, RPAREN, LBRACE, RETURN, 0, SEMI, RBRACE].Parser.parse()builds parse tree rooted atFunctionDef(main)with childReturn(0).NodeBuilder.build()connects visual/source nodes — attachesReturnnode undermainblock.CodeGenerator.emit()walks the node tree and emits bytecodeiconst_0; ireturn.- Facade returns
Bytecodeto client. The client never held aScannerorParserreference and never chose the order; the facade did.
Switching to gcc -E (preprocess only) would call a different facade method preprocessOnly() that stops after scanning — same facade, different default bundle.
Assumptions & Scope — When Facade earns its keep.
Use it when: many clients share the same complex set of subsystems and would otherwise each rebuild the same wiring; you want a default view for most clients while still allowing deeper access for a few; subsystem implementations are likely to change and you want that change localised.
Do not add it when: a subsystem is used alone or in only one simple way — a direct call is clearer and avoids an extra layer; the added indirection would hide useful flexibility that most clients actually need — a facade that is too opinionated forces clients to work around it.
Sizing note: A facade should not become a God object that accumulates unrelated responsibilities. Keep it focused on orchestration and hiding, not on business logic. If the facade grows business rules of its own, split them behind it into the subsystems it already hides.
Visual: picture a modern house front door. Behind the door are dozens of systems — wiring, plumbing, heating, security, kitchen appliances — each with its own switches and dials. Visitors use the front door; they do not crawl through the service ducts. An electrician can still open the service panel when needed, but most people never do.
13.10.3 How to Recognise a Facade
Look for telling phrases in a problem statement: single entry point, default view, level in front of the subsystems, bundle the scanner–parser–builder–generator steps. The structural signature is:
- A class frequently named
Compiler,POSRuleEngineFacade,ServicesFacadeorSystemFacade. - It holds references to subsystem objects (often via composition).
- Clients communicate with subsystem classes by sending requests to the facade, and the facade controls the conversation with the subsystems — it forwards, orders and adapts.
The lecture connects the facade idea to the earlier GRASP Controller idea: a controller takes delegation and coordination work away from scattered clients and places it in one place that knows the workflow. Facade is the GoF expression of that same impulse for subsystem collections. The textbook companion traces the purchase example further into a POSRuleEngineFacade whose static call POSRuleEngineFacade.getInstance().isInvalid(sli, this) hides whether rules are evaluated by Strategy objects, an interpreter, or a commercial rule engine — the facade makes that choice interchangeable.
Pitfall — Facade vs. Adapter vs. Mediator. Facade hides a subsystem and offers a simpler default view — its primary gain is reduced coupling, not interface repair. Adapter repairs an existing interface mismatch between one target and one adaptee — its gain is reuse without modification. Mediator coordinates peers that already know about each other and would otherwise be tightly meshed. On the exam, justify the choice: "We need a single default entry over five subsystems, not a one-to-one interface fix, so Facade not Adapter."
Real-world settings cited as textbook facades: gcc invoked from Linux; purchase APIs that hide a payment-rule engine (credit, debit, coupon, gift certificate, expiry); many library entry objects that bundle scanner, parser, builder and generator steps; and the DBFacade / POSRuleEngineFacade examples in the companion design case where dozens of classes are hidden behind one public access point accessed via Singleton.
Q & A — The two facade questions that framed the lecture.
Q: What are the different steps in compilation that a facade would hide? A: Steps named in the lecture are lexical analysis by the Scanner, syntax analysis by the Parser that builds a parse tree, node building that creates and connects the various nodes (visual and source nodes, basic blocks), and code generation that emits final code or bytecode. Additional steps such as handling byte streams and intermediate forms appear in different compiler designs. The facade knows the order and which parts need which data, so the client does not. That is the wiring the facade owns.
Q: Do we need facade for every subsystem? A: No. Facade is best when many clients share the same complex set of subsystems and would otherwise each rebuild the same wiring. If a subsystem is used alone or in only one simple way, a direct call may be simpler. The pattern trades a small extra layer for reduced coupling and fewer mistakes — you add the layer only when the saving outweighs the indirection.
Exam note: When a problem describes many interacting subsystem classes and says you want simple client code and fewer dependencies, propose a Facade. Identify the facade class and the subsystems it hides (e.g., Compiler over Scanner, Parser, NodeBuilder, CodeGenerator or POSRuleEngineFacade over payment-rule evaluators), state the forwarding/ordering the facade performs, and explain how future subsystem changes stay local to the facade. If the subsystem needs global reach, note that facades are often accessed via Singleton.
Recap — Facade. Problem: complex subsystem collection with tangled wiring. Solution: single entry object that holds subsystem references, forwards client requests in the right order, and shields clients from subsystem types. Gain: default simplicity and localised change. Cost: extra layer; risk of a too-opinionated front. Signature phrase: "single entry / default view in front of subsystems."
13.11 Adapter — Using What You Have with the Interface You Need
13.11.1 The Problem
Hook — The wall socket abroad. You land in a country with a different wall socket. The electricity is the same — 230 V, same physics — but the shape of the hole does not match your plug. You do not rewire your laptop or rewire the hotel. You slip in a small plastic adapter that translates the shape. One side fits your plug; the other side fits the wall. The power flows unchanged. That shape-translation is exactly what Adapter does for interfaces.
How do you reuse an existing class whose functionality is right but whose interface does not fit the place where you need it, without changing that existing class? The lecture uses that electrical adapter directly: electricity is unchanged, only the shape of the interface differs.
In code the same mismatch arises often. The lecture's running example is the Icon family in Java. An Icon can paint itself (paintIcon) and can report its width and height (getIconWidth, getIconHeight), but it is not a Component and cannot be added directly to a Container. The Swing containers — JFrame, JPanel — accept only Component objects (more precisely, JComponent). You want to show icons inside containers without rewriting either side. The desired functionality already exists; the interface is what is wrong.
A similar mismatch appears with text boxes, graphical widgets, device drivers and external service clients where one hierarchy offers the right behaviour but the wrong type for the client that will hold it. The textbook companion adds the NextGen examples: TaxMasterAdapter and SAPAccountingAdapter adapt varying external tax and accounting service APIs to the consistent ITaxCalculatorAdapter / IAccountingAdapter interfaces used inside the application — same shape-translation, different domain.
The adapter situation. You have a Target interface the client expects, an Adaptee class that already does the work under a different interface, and you are not allowed (or do not want) to change the Adaptee because other clients depend on it. The two interfaces are conceptually related — they do similar things under different names or with different signatures. You cannot adapt unrelated functionality meaningfully by wrapping alone.
13.11.2 The Solution Shape
Purpose — Interface translation by wrapping. Adapter, also called Wrapper, bridges a target interface the client expects and an existing adaptee interface that already does the work. The adapter translates each target operation into one or more adaptee operations and, where needed, supplies the small extra behaviour the target requires that the adaptee lacks.
Structure used in the lecture (object-adapter form, the more common and flexible variant):
- Target interface — what the client expects. In the Icon example,
Component/JComponent, with operations such aspaintComponent(Graphics g)andgetPreferredSize(). In the NextGen example,ITaxCalculatorAdapterwithgetTaxes(Sale). - Adaptee class — the existing class you want to reuse, such as
Iconwith operationsgetIconWidth(),getIconHeight()andpaintIcon(Component c, Graphics g, int x, int y), or an externalGoodAsGoldTaxProwith its own API. - Adapter class — implements or extends the Target, holds a reference to the Adaptee as an aggregated private member, and translates each Target operation into a call on the Adaptee. The adapter therefore shows both inheritance (it is a Component) and aggregation (it has an Icon).
A client holds a reference to the target interface and talks only to the adapter. Inside, the adapter forwards the call. Class-level adapters achieve the same translation via multiple inheritance (Adapter inherits Target interface and Adaptee implementation), but Java without multiple inheritance of implementation favours the object-adapter with composition — the form shown in the lecture.
The code sketch presented and elaborated for precision:
interface Icon {
int getIconWidth();
int getIconHeight();
void paintIcon(Component c, Graphics g, int x, int y);
}
class IconAdapter extends JComponent {
private Icon icon; // aggregation — adapter HAS an adaptee
IconAdapter(Icon icon) { this.icon = icon; }
@Override
protected void paintComponent(Graphics g) {
// translation: target paintComponent → adaptee paintIcon
icon.paintIcon(this, g, 0, 0);
}
@Override
public Dimension getPreferredSize() {
// translation: target size query → adaptee width/height queries
return new Dimension(icon.getIconWidth(), icon.getIconHeight());
}
}
// client in a Frame — talks only to Target
Icon carIcon = new CarIcon(); // also BirdIcon, any Icon
JComponent adapted = new IconAdapter(carIcon);
frame.add(adapted); // frame sees a JComponent; drawing delegates to Icon
Here target methods paintComponent and getPreferredSize are implemented by calling adaptee methods paintIcon, getIconWidth and getIconHeight. Once the icon has been wrapped as a component it can be added to the JFrame and will be drawn and sized correctly. This matches the lecture description where IconAdapter extends JComponent, implements target methods, holds a private Icon, and uses it in paintComponent and getPreferredSize.
Alternative adaptees were also mentioned as possible: any icon type such as CarIcon or BirdIcon can be given to the same adapter without changing the container code — one adapter serves an entire adaptee hierarchy.
The same translation in the textbook companion:
«interface» ITaxCalculatorAdapter { List<TaxLineItem> getTaxes(Sale s); }
class TaxMasterAdapter implements ITaxCalculatorAdapter {
private TaxMaster adaptee = new TaxMaster(); // private aggregated adaptee
List<TaxLineItem> getTaxes(Sale s) { return adaptee.calculateTax(s); } // name translation
}
Worked trace — Icon → Component with real calls.
- App loads
Icon icon = new CarIcon();—icon.getIconWidth()returns 64,getIconHeight()returns 64,paintIconknows how to draw a car glyph. - App wraps:
JComponent comp = new IconAdapter(icon);— adapter stores the reference. - Layout manager asks
comp.getPreferredSize()→ adapter returnsnew Dimension(64,64)(translating the two width/height calls). The container reserves a 64×64 rectangle. - Repaint fires: Swing calls
comp.paintComponent(g)→ adapter executesicon.paintIcon(this,g,0,0)→ car glyph appears at (0,0) inside the reserved rectangle. - Replace the adaptee:
icon = new BirdIcon(); comp = new IconAdapter(icon);— no change to theJFramecode; the frame still callspaintComponentandgetPreferredSize.
Sense-check: we reused CarIcon and BirdIcon without editing either class, and the client (JFrame) never references Icon directly — only the adapter does.
Assumptions & Scope — When translation makes sense.
Use it when: you want to use an existing class without changing it; the functionality you need is already there but the interface you need is different; the two interfaces are conceptually related enough that a translation of operations makes sense (both paint, both calculate tax, etc.); modern code may already provide such bridges and spotting the target–adaptee–adapter triple helps you reuse library code rather than duplicating it.
Do not force it when: the adaptee's behaviour does not match the target's contract even under translation — wrapping cannot invent unrelated behaviour without becoming a rewrite; the mismatch is small and you own the adaptee's source — directly changing the adaptee may be cleaner if you control all clients.
Visual: picture two connectors side by side — a three-round-pin plug and a two-flat-pin socket. The adapter is a block with a socket on one face (fits your plug → Target) and pins on the opposite face (fits the wall → Adaptee). No electricity is changed; only the shape through which it flows is translated.
13.11.3 When to Use It
Use Adapter whenever you meet all three conditions:
- You want to reuse an existing class without changing it (because its source is unavailable, belongs to a library, or is used elsewhere in its current form).
- The functionality you need is already there but the interface you need is different — names differ, parameter order differs, or the type hierarchy does not match the container's expectation.
- The two interfaces are conceptually related enough that a translation is meaningful. Adapting
Iconpaint/size toComponentpaint/size works; adapting a tax calculator to a drawing routine would not.
The pattern is structural because it reshapes the interface around the reused implementation, not because it adds new domain behaviour. It realises a classic Protected Variation + Indirection theme from GRASP: protect the client from varying external interfaces by inserting an indirection object that applies polymorphism.
Related families worth placing next to Adapter:
- A resource adapter that hides an external system behind a consistent interface may also be considered a Facade (a single front over a subsystem). The textbook makes the distinction: if the motivation is adaptation to varying external interfaces, call it Adapter even if it looks like a Facade — intent selects the name.
- Bridge also separates abstraction from implementation, but it is designed up-front so both sides can vary independently, whereas Adapter repairs a mismatch after the fact.
- Decorator enhances without changing interface; Adapter changes interface.
Pitfall — The one question that certifies understanding.
Students often propose Adapter whenever any wrapping occurs, or claim a copy constructor can replace an adapter. Translation of type vs. copying of values is the test: a copy constructor copies fields; an adapter changes the type through which you reach those fields. Here the gap is not data duplication but interface mismatch — only wrapping solves it. If your answer's core verb is "copy," it is not Adapter; if the verb is "translate / forward / wrap," it is.
Q & A — Two checks from the lecture.
Q: How is Adapter different from just changing the Adaptee to match the new interface? A: Changing the adaptee would break its other users and would couple you to its source. Adapter leaves the adaptee unchanged and adds a thin layer that presents the expected interface to new clients. Existing clients keep the old interface; new clients see the target. The client gains the new interface while the existing class keeps its current form and its current clients — that preservation is the point.
Q: Could a copy constructor replace the adapter role here? A: No. A copy constructor copies values from one instance to another — it changes data. An adapter changes the type through which you reach those values — it changes interface. Here the problem is not "I need another copy of the icon's pixels" but "I need to reach the icon's paint ability through JComponent methods so a container will accept it." Copying does not make a JComponent; wrapping does.
Real-world settings: electrical socket adapters, Icon-to-Component (IconAdapter extending JComponent, holding a private Icon, implementing paintComponent by calling paintIcon and getPreferredSize by calling getIconWidth/getIconHeight, then added to a Frame), many Swing wrapper classes that make one toolkit fit another, and the NextGen IAccountingAdapter / ITaxCalculatorAdapter families that adapt external service APIs to the application's stable interfaces. The textbook notes that manufacturers' JavaPOS classes com.ibm.pos.jpos.CashDrawer and similar are also adapters in this sense — adapting native device drivers to the standard jpos.CashDrawer interface.
Exam note: When a problem says an existing useful class cannot be used directly because it does not implement the required interface and you should not change it — propose an Adapter. Name the target (what the client expects), the adaptee (the existing class with the wrong interface) and the adapter (the translator). Show the aggregation link from adapter to adaptee and the inheritance / implementation link from adapter to target, and state that each target call is translated into adaptee call(s). Naming the three roles correctly is the examiner's first check.
Recap — Adapter. Problem: right behaviour, wrong interface. Solution: adapter implements target and holds adaptee, translating each target call into adaptee calls. Gain: reuse without modification, stability against varying external interfaces. Cost: extra indirection, translation maintenance. Distinct from Facade (simplify many) and Bridge (design up-front).
13.12 Anti-Patterns — Learning from Failure
13.12.1 What Anti-Patterns Are
Hook — Why study the wrong answer? Imagine a multiple-choice question with options A, B, C, D. One way to find the right answer is to evaluate each option positively. Another, often faster, way is to rule out the wrong answers first — to recognise that A cannot be right because it leaks coupling, and C cannot be right because it copies code. When you eliminate the wrong constructions confidently, the good answer remains. Anti-patterns give you that elimination skill for design.
An anti-pattern — a recurring bad solution that should be avoided because it reliably leads to poor outcomes — is the mirror image of a pattern. While a pattern records a proven good solution to a recurring problem, an anti-pattern records a worst practice or a bad lesson that teams have learned through failed projects. The reason to study anti-patterns is the same as the reason to study good patterns: humans learn a great deal from failures. If you learn how a bad design arises, what forces tempt you into it, why it fails, and how to move out of it toward a good solution, you avoid repeating the same mistake.
This idea is framed in the lecture with that multiple-choice analogy. One way to solve a multiple-choice question is to argue positively for the correct option. Another way is to rule out the wrong solutions first. By identifying and removing the wrong answers you also arrive at the good answer, and you arrive there with stronger justification — "we chose Observer because the alternatives were a polling loop that wastes CPU and a mesh of direct references that couples every view to every other view." Anti-patterns help you rule out the designs you should not use. Absence of anti-patterns is therefore part of what makes a design good.
A useful distinction: an anti-pattern is not simply "not knowing a pattern." Not knowing a pattern just leaves you to invent from scratch with no guidance. An anti-pattern is a named, repeatable bad construction that many teams have tried independently and that has been shown to cause harm — copy-paste variants, monolithic God objects, global cargo-cult singletons applied everywhere, a mesh where every view talks directly to every other view instead of through Observer, or presentation logic scattered into domain objects. Knowing its name lets you call it out in a review ("this is a God-object risk") and replace it with the corresponding good pattern.
What makes an anti-pattern worth naming. A documented bad recipe shares the same headings as a good pattern, but turned: problem and tempting context → bad solution that looks plausible → why it fails (consequences, forces it violates) → how to refactor toward the good solution. Naming the bad form makes it discussable, detectable in reviews, and avoidable by juniors who would otherwise rediscover it.
13.12.2 How They Are Documented
Anti-patterns are documented in the same habit as good patterns — problem, context, bad solution, why it fails, how to move to a better solution — but with the focus on the failure path and recovery. Bad practices are explicitly labelled so teams can recognise them in reviews and steer away. Consequences are stated as reliably negative: higher coupling, lower cohesion, duplicated code, fragile change, testing difficulty, performance traps.
The lecture notes that time may not allow a deep dive into anti-patterns in this session, but the idea should be retained: catalogue the bad forms alongside the good forms. The habit extends naturally to related forms such as design smells (symptoms of a drift toward an anti-pattern) and refactorings (the moves that cure them). That extended habit is why the textbook companions often pair a pattern with the alternative that fails — for instance, contrasting Facade (one default front) with a scatter of direct subsystem calls that entangle every client.
Study tip: for each pattern you revise, add one anti-pattern question to your card: "What would the naïve bad construction look like here?" For Observer the bad form is "every view polls the model or holds a direct typed reference to every other view." For Composite the bad form is "a separate List<Leaf> everywhere with no uniform interface, so every client branches on type." For Singleton the bad form is "every class creates its own new Configuration() and the values diverge." Naming that failure makes the good pattern's intent sharper.
Scope — What the lecture expects you to do. You are not expected to memorise a separate catalogue of named historical anti-patterns (God Object, Blob, Lava Flow, Poltergeist) for this exam beyond the lecture's own framing. You are expected to be able to argue anti-pattern avoidance when you justify a pattern: state the good pattern that fits and the superficially similar but wrong construction that would arise if you chose badly, and why that wrong choice fails. That two-sided justification is the consequence-aware answer format the examiners reward.
Q: Are anti-patterns the same as simply not knowing a pattern? A: No. Not knowing a pattern leaves you to invent with no precedent — you may still stumble on a good solution. An anti-pattern is worse than ignorance: it is a named, repeatable bad construction that many teams have tried and that has been shown to cause harm — so recognising its name lets you call it out and replace it before it ships. The lecture stresses that distinction because conflating the two hides the value of catalogue-based elimination in reviews.
Real-world texture: the most informal anti-pattern exercise happens in project post-mortems and interviews that ask "what is the worst way to solve X, and how would you rescue it?" Recurring answers — "we tried direct global references everywhere and regretted it," "we copy-pasted calculation code into every form and maintenance collapsed" — are nascent anti-pattern entries waiting to be named.
Recap — The mirror. A pattern is a recurring good solution; an anti-pattern is a recurring bad solution that should be avoided. The multiple-choice ruling-out strategy captures the value: knowing what not to do narrows the design space reliably. Document anti-patterns like patterns (context, bad solution, why it fails, remedy) and use them as elimination arguments in reviews and exam answers.
Bridge forward. With the idea that designs are judged not only by what they include but also by what failures they avoid, the lecture moves to its closing guidance: how to combine several patterns in one real problem and what industry settings look like when patterns are in everyday use — the focus of Exam Guidance and Key Industry Applications.
Exam Guidance Summary
This lecture closes with the pattern-combination skill that the exam will measure directly. Individual pattern knowledge is necessary but not sufficient.
- Expect one or more real-world problems where you must identify several patterns at once, place them in one object-oriented solution, show the arrangement with class diagrams and sequence or interaction diagrams plus pseudocode, and argue that the combination works in the given context. A typical problem might ask you to introduce a factory that creates adapters, accessed via singleton, with a facade over a rule subsystem — and to justify each choice. Practise placing two to three patterns together rather than hunting for a single pattern, and rehearse the adaptation sentence: "We kept the GoF collaboration but mapped roles to domain names X, Y, Z because..."
- You are expected to know the 23 GoF patterns at the level of name, intent, problem, context, structure, participants, collaborations and consequences. The syllabus includes this full set, and for this programme there is no later course that will repeat it at this depth. All patterns are freely documented; the GoF book with C++ examples and their Java interface translations is the core reference. Use the minimal documentation checklist when you revise: name, problem with intent and context and when to apply, solution in terms of diagrams and pseudocode, and consequences with trade-offs. Add known uses to anchor context — Swing is the richest single source.
- Study by doing, not only by reading. Read a pattern online, download its code, run it, trace through the calls with a debugger (set breakpoints in
clone(),getInstance(),notifyObservers(),paintComponent()and watch the collaborations fire), and then apply it in a different domain. The Swing library is named explicitly as a useful place to see observer, composite, factory, singleton, iterator, facade and adapter side by side in production code. The prototype Shape–Circle–Rectangle example and the Icon-to-JComponent adapter are both runnable starting points; extend them to your own types to test adaptation.
- On the exam, write assumptions so the reader can follow your reasoning when you choose a pattern. Adapt the general structure to the problem and name the adaptation rather than copying diagrams line for line — examiners check that you preserved intent while renaming. Show the translation steps, including any reuse of existing interfaces versus new interfaces you introduced. Marks are distributed across correct pattern choice, correct diagram shape, and coherent justification of consequences and trade-offs — including which anti-pattern you ruled out and why.
- Prior learning referenced as helpful includes object-oriented principles already covered: abstraction, encapsulation, modularity, separation of concerns, coupling and cohesion, divide and conquer, single point of reference, separation of interface from implementation, separation of policy from mechanism, and sufficiency with completeness. The Smalltalk MVC and pipes-and-filters architecture mentioned in the session are background context you should be able to relate to pattern choices — for instance, MVC as the historical home of Observer, pipes-and-filters as a high-level architecture that sits above patterns.
Key Industry Applications
The patterns taught in this lecture are not academic exercises — the lecture and its textbook companions present them as the de facto way that design knowledge is recorded and reused across real industries.
Catalogues beyond software. Gang of Four patterns are described as the de facto standard for documenting design knowledge. Domain catalogues now exist for telecommunications, web systems and retail selling; teams in those domains publish their own pattern sets that follow the same name, problem, context, solution and consequence format. When a telecom team says "we need a Mediator for the session manager," the same shorthand benefit appears as in software.
Vocabulary that travels. Dress and car pattern vocabularies are the non-software parallels used to explain why a short name carries a full solution. A seller hearing gown, saree, plazo or a buyer hearing hatchback, SUV knows the silhouette, proportions and trade-offs without a long description — just as a developer hearing Factory, Observer or Prototype immediately pictures the collaboration. That cross-domain parallel is retained deliberately to anchor why intent and context must accompany the name.
Borrowed experience at scale. The 15 to 20 bridges or buildings analogy for how long it takes to become an expert by building alone is used to motivate borrowing experience through patterns rather than waiting for personal project count to grow. Telecommunications and product-line companies use pattern histories for exactly this reason — to let a new hire design a new switch variant by standing on the recorded lessons of past switches.
Frameworks that embed Observer. Model-View-Controller in Smalltalk and now broadly in UI frameworks (Swing, JavaFX, web MVC) is the framework history behind Observer. Data tables with pie chart, histogram and scatter views are the runnable picture of one model with many views kept in step by notification. The blackboard with several student observers is the physical classroom parallel for the same one-to-many setup. In production today this shows up as event delegation, publish-subscribe channels, message buses and reactive streams.
Editors that rely on Prototype and Composite. Graphical editors in word processors, PowerPoint and Visio plus many UML editors are the running context for Prototype. The Shape interface with Circle and Rectangle, plus a shape cache that holds prototypical instances and hands out clones, is the code example widely available online and used to practise the pattern. The same editors and file explorers show Composite: a Folder may hold Files or other Folders; a Picture may hold Shapes; JComponent/Container in Swing composes components uniformly — each works whether it holds one child or a deep tree.
Swing as a living pattern catalogue. Java Swing for graphical user interfaces is presented as a library that uses almost every GoF pattern in one place. Working with Swing components, containers, lists, tables and listeners exposes Composite (Container), Observer (Listener/Model), Factory and Abstract Factory (look-and-feel creation), Singleton (toolkit), Iterator (collections), Facade (entry components), Adapter and Decorator side by side in real code. Cloneable as a tagging interface and the IconAdapter that extends JComponent and holds a private Icon — implementing paintComponent by calling paintIcon — are two Java details inside that library that the lecture singles out.
Architecture that sits above patterns. Linux command pipelines (cat | grep | sort) illustrate the pipes-and-filters architecture that sits above patterns, and the GCC command that drives Scanner, Parser, NodeBuilder and CodeGenerator inside a compiler — handling code streams, parseTree, visual/source nodes, bytecode — is the canonical Facade example. Recognising which level (pattern vs. architecture) a problem belongs to is itself a design skill the lecture reinforces.
Shared resources and rule engines. Company name objects and random-number generators shared across an application are the two small examples used to motivate Singleton — extended in industry to configuration holders, loggers and registries. Payment handling where one purchase() call hides credit card, debit card, coupon and gift certificate rule evaluation — and the POSRuleEngineFacade that may hide Strategy objects, an interpreter or a commercial rule engine — is the business Facade example drawn from the textbook's NextGen case study. The same study's ServicesFactory accessed via Singleton and returning Adapters formed from an Abstract Factory family (IBMJavaPOSDevicesFactory vs. NCRJavaPOSDevicesFactory) shows how Adapter, Factory, Abstract Factory, Singleton and Facade combine in one industrial flow.
The everyday adapter. Electrical socket adapters used when visiting a country with a different socket are the everyday parallel for Adapter. Inside software the IconAdapter that extends JComponent, holds a private Icon, implements paintComponent by calling paintIcon and implements getPreferredSize by calling getIconWidth/getIconHeight, and is then added to a Frame, is the concrete code example. The same shape appears when JavaPOS driver classes such as com.ibm.pos.jpos.CashDrawer adapt native drivers to the standard jpos.CashDrawer interface, and when LocalProducts and DBProductsAdapter share the IProductsAdapter front.
OODAP Lecture 13 notes · Design Patterns — Gang of Four Solutions
Sections Breakdown
Defines design pattern as a named, proven, contextual solution; introduces Gang of Four book, the 15-to-20 bridges analogy for slow expertise, dress and car name vocabularies, and the two exam threads.
Explains daily benefits via shared vocabulary and borrowed experience, the de facto standard across industries, and why every pattern description must include consequences and trade-offs.
Presents the minimal four-part documentation (name, problem with intent/context, solution with UML/pseudocode, consequences), interface-based solution language, intent+context study order, and hands-on learning via Swing and Shape examples.
Distinguishes low-level copyable templates (loops), medium-level adaptable patterns (contextual solutions), and high-level architectures (pipes-and-filters), with Christopher Alexander as the origin and iterator as the example.
Classifies 23 GoF patterns by purpose into creational (flexible creation), structural (composed skeletons) and behavioral (distributed responsibilities), plus class vs object scope and the collaboration-in-context view.
Covers Prototype's problem (unknown number of copies from a canvas), solution via Shape interface with Circle/Rectangle and a registry clone, tagging interface Cloneable vs copy constructor, and shallow/deep copy trade-offs.
Presents Singleton's problem (one shared resource such as company name or random generator), solution via private constructor plus private static instance plus public static getInstance, and coupling/test pitfalls.
Explains Composite's part-whole problem (pen single vs bundle, sound/video clip vs Sequence), solution via common MediaClip interface plus inheritance for uniformity plus aggregation for containment and forwarding of play/getPrice.
Covers Observer's one-to-many problem (table → pie, histogram, scatter), Smalltalk MVC origin, Subject/Observer with attach/detach/notify and hasChanged, pull vs push, and publish-subscribe / listener applications.
Presents Facade's problem (many interacting subsystems such as scanner, parser, node builder, code generator behind gcc), solution as single entry that forwards and orders requests, and single-default-view vs power-client trade-off.
Covers Adapter's interface-mismatch problem (Icon vs JComponent), object adapter via inheritance plus aggregation with IconAdapter holding Icon and translating paintComponent/getPreferredSize to paintIcon/getIconWidth/Height, and distinction from Facade.
Defines anti-pattern as a recurring bad solution worth naming, documents it with context, bad solution, failure reasons and remedy, and frames it as multiple-choice elimination alongside pattern knowledge.
Exam expects combination of several patterns with diagrams, pseudocode and justification; know 23 GoF patterns by name, intent, context, structure and consequences; practise via runnable Swing and downloaded examples.
Exam expects combination of several patterns with diagrams, pseudocode and justification; know 23 GoF patterns by name, intent, context, structure and consequences; practise via runnable Swing and downloaded examples.
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.
Foundations of Design Patterns
Must-know: A pattern is a named, proven, contextual description of communicating objects/classes customised to a recurring design problem; GoF (Gamma, Helm, Johnson, Vlissides, 1994-95, foreword Grady Booch) made it the Bible.
⚠️ Top pitfall: Treating pattern names as decoration without showing interface, context and consequences
Self-check: Why are dress names like gown/saree/plazo used to explain pattern names?
Connects to: 13.2, 13.3
Benefits, Shared Vocabulary and Consequences
Must-know: Patterns give shared vocabulary, borrowed experience, reuse and systematic documentation; every pattern has gains and costs and a full description states when not to apply it.
⚠️ Top pitfall: Remembering only advantages and ignoring liabilities so the design choice cannot be justified
Self-check: How do you test whether a description is a full pattern or just a diagram?
Connects to: 13.1, 13.3
How a Pattern Is Described and How to Learn It
Must-know: Minimal documentation is name + problem (intent/context/when) + solution (class/sequence diagrams + pseudocode) + consequences; study intent and context before diagrams, and learn by running and adapting code (Swing is the live catalogue).
⚠️ Top pitfall: Memorising diagrams first and matching problems to diagrams instead of intent to context
Self-check: What four headings must a complete GoF pattern sheet contain?
Connects to: 13.2, 13.4
Granularity — Where Patterns Sit
Must-know: Patterns are medium granularity — adaptable contextual solutions — between copyable low-level templates and course-level architectures like pipes-and-filters and client-server; origin is Christopher Alexander (civil engineer).
⚠️ Top pitfall: Treating a pattern as a pasteable template instead of an adaptation with renamed roles
Self-check: Why is an Iterator a pattern but a for-loop is not?
Connects to: 13.3, 13.5
The Three Families of GoF Patterns
Must-know: Purpose families are creational (how objects appear), structural (how parts compose), behavioral (how responsibilities flow); scope is class (inheritance, compile-time) vs object (composition, run-time).
⚠️ Top pitfall: Choosing a family by diagram shape instead of by design force (creation vs composition vs communication)
Self-check: If the symptom is rigid object creation tied to one class, which family do you search first?
Connects to: 13.4, 13.6
Prototype — Cloning Objects from a Model
Must-know: Prototype creates by cloning a model via a common interface and registry; use when clone count is not known until runtime and concrete class should stay hidden; Cloneable is a tagging (marker) interface with no methods.
⚠️ Top pitfall: Implementing Cloneable without overriding clone() and expecting a public method, or sharing mutable state via shallow copy
Self-check: When would you prefer a copy constructor over Object.clone() and why does intent stay the same?
Connects to: 13.5, 13.7
Singleton — One Instance with Global Access
Must-know: Singleton guarantees one instance via private constructor + private static instance + public static getInstance(); clients call getInstance() not new; interview signature for private constructors.
⚠️ Top pitfall: Overusing singleton and hiding global mutable coupling that makes tests order-dependent
Self-check: Why must the constructor be private and what fails if it is not?
Connects to: 13.6, 13.8
Composite — Treating a Single Object and a Group the Same Way
Must-know: Composite makes a group usable as a single item via one common interface; composite both implements the interface and aggregates components, forwarding play/price; inheritance gives uniform type, aggregation gives containment.
⚠️ Top pitfall: Calling a List<Clip> a composite without the inheritance link and forwarding loop
Self-check: Why does a Sequence need both inheritance from MediaClip and aggregation of MediaClip?
Connects to: 13.7, 13.9
Observer — When One Change Must Flow to Many Views
Must-know: Observer keeps many views in sync via one-to-many broadcast: Subject holds list with attach/detach/notify (checked by hasChanged), Observer has update(); subject knows only the interface; pull via getState() or push via argument.
⚠️ Top pitfall: Letting the subject mention concrete observer names or forgetting the open-ended dependents requirement
Self-check: How does Observer generalise the Smalltalk MVC idea?
Connects to: 13.8, 13.10
Facade — A Simple Door in Front of a Complex House
Must-know: Facade hides a subsystem collection behind one entry class that holds subsystem references and forwards client requests in the right order; clients see one dependency instead of many; change stays local to the facade.
⚠️ Top pitfall: Using Facade as a God object that accumulates business logic instead of staying an orchestrator
Self-check: Why is gcc a better illustration of Facade than a single class with one helper?
Connects to: 13.9, 13.11
Adapter — Using What You Have with the Interface You Need
Must-know: Adapter translates an existing adaptee interface to the target interface the client expects; object adapter implements Target and holds Adaptee privately, forwarding each target call to adaptee; use when you cannot change the adaptee and interfaces are conceptually related.
⚠️ Top pitfall: Confusing Adapter (fix one interface mismatch) with Facade (simplify many subsystems) or thinking a copy constructor can replace wrapping
Self-check: In IconAdapter, which class is Target, Adaptee and Adapter, and which aggregation translates which calls?
Connects to: 13.10, 13.12
Anti-Patterns — Learning from Failure
Must-know: Anti-pattern is a named, repeatable bad construction that reliably causes harm; study it with the same headings as a pattern plus a migration path to the good solution; elimination is part of a complete answer.
⚠️ Top pitfall: Equating anti-pattern with simply not knowing a pattern — an anti-pattern is actively harmful, not merely absent knowledge
Self-check: For Observer, what is the naive bad construction that an anti-pattern entry would catalogue?
Connects to: 13.11, 13.1
Exam Guidance Summary
Must-know: Exam tasks combine several patterns in one design with named adaptation and trade-off argument; study each pattern by name, intent, context, diagrams and consequences, and practise running code.
⚠️ Top pitfall: Drawing a diagram without stating adaptation or consequence justification
Self-check: What three marking dimensions does an exam answer need for each pattern used?
Connects to: 13.1, 13.3
Key Industry Applications
Must-know: Industry uses the same catalogue habit — Swing shows Composite, Observer, Adapter, Facade together; GCC shows Facade; IconAdapter/JavaPOS show Adapter; Prototype and Composite appear in editors and file systems.
⚠️ Top pitfall: Claiming patterns are academic when industry catalogues explicitly reuse the same name-problem-solution-consequence format
Self-check: Name the pattern that Linux pipes vs GCC vs IconAdapter each illustrate as an industry application
Connects to: 13.6, 13.8, 13.11
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.