Throughout the course we have built patterns on top of PideYa; this lesson flips the telescope around: the patterns you have been using for years without knowing it. Every time you traverse a list with a for-each, wrap a stream in a BufferedReader or let Spring inject a bean for you, you are consuming Iterator, Decorator and Singleton written by others. Reading these examples has double value: they confirm that the catalog is not academic theory but the engineering behind the world's most-used libraries, and they train the most profitable skill of this lesson — recognizing a pattern while reading someone else's code from its clues, starting with the names. We won't talk about architecture here (layers, microservices, messaging): that is module 6 territory; here we look at concrete classes and APIs.
Contents
- Patterns in the JDK
- Patterns in Spring
- Other libraries and frameworks
- How to recognize patterns when reading someone else's code
- What naming conventions teach
- Exercises and conclusion
Patterns in the JDK
Java's standard library is the largest catalog of patterns in production on the planet:
| Pattern | Where it lives in the JDK | The clue |
|---|---|---|
| Iterator | java.util.Iterator, Iterable, the whole for-each |
The pattern was promoted to language syntax |
| Decorator | java.io: BufferedReader, GZIPOutputStream... |
Constructors that receive another stream of the same type |
| Observer | Swing listeners (ActionListener), java.util.concurrent.Flow |
addXxxListener, subscribe |
| Factory Method | Integer.valueOf, List.of, Optional.of, Files.newBufferedReader |
Statics that return the type... or a subtype you don't get to choose |
| Builder | StringBuilder, HttpRequest.newBuilder(), Stream.Builder |
Chainable methods + a final build() |
| Proxy | java.lang.reflect.Proxy (the foundation of half the ecosystem) |
The JDK ships the pattern as a built-in utility |
| Strategy | Comparator in sort, ThreadFactory, UncaughtExceptionHandler |
The algorithm travels as an argument |
| Template Method | AbstractList, InputStream.read(byte[]) on top of read(), ClassLoader.loadClass |
An Abstract* class with methods to "fill in" |
| Composite | Swing: Container is a Component that contains Components |
The container implements the interface of the content |
| Flyweight | The Integer.valueOf cache (−128..127), the string pool |
Shared instances for repeated values |
| Adapter | Arrays.asList, Collections.list(Enumeration), InputStreamReader |
Converts one interface (array, Enumeration, bytes) into another (List, Iterator, chars) |
| Prototype | Object.clone() / Cloneable |
The mechanism exists... with the caveats we saw in 02-06 |
The java.io case deserves a zoom-in, because it is the same stacking as PideYa's dish extras:
// Three decorators stacked on a core, composable in any reasonable order:
var reader = new BufferedReader( // adds buffering and readLine()
new InputStreamReader( // adapts bytes → characters (an Adapter in the stack!)
new FileInputStream("orders.csv"))); // the concrete component
// And the head-to-head, live: Integer.valueOf is Factory Method WITH Flyweight inside
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
// a == b is true: valueOf returned the SAME cached instance (-128..127).
// With new Integer(100) (deprecated) they would be distinct objects: the factory
// exists precisely so that decisions like this can be made without the client knowing.That last comment is module 2's entire creational moral (02-07): whoever controls creation controls sharing, caching and the subtype — which is why the modern JDK (List.of, Optional.of) barely lets you write new anymore.
Patterns in Spring
Spring doesn't "use" patterns: it is patterns assembled, and several of them resolve critiques we made during the course:
- Singleton per container — a bean's default scope: one instance... per
ApplicationContext, not per JVM. It is exactly the alternative we defended in 02-02: uniqueness managed by whoever injects, no staticgetInstance(), no untouchable global state in tests. PideYa'sPideYaConfigended up this way. - Factories everywhere —
BeanFactory/ApplicationContextare giant factories; theFactoryBean<T>interface lets you write your own;@Beanin a@Configurationclass is a Factory Method the container invokes for you. - Dynamic Proxy as the backbone —
@Transactional,@Cacheable,@Asyncand method security work because Spring wraps your bean in a proxy (JDK dynamic if there is an interface, CGLIB otherwise) that intercepts the call, does its magic and delegates. It is module 3's Proxy industrialized — and it explains the classic "my@Transactionaldoesn't work when I call myself": the self-call never goes through the proxy. - Template Method without inheritance —
JdbcTemplate,RestTemplate,TransactionTemplate: the skeleton (open connection, execute, map, close, translate exceptions) is fixed; your variable step comes in as a lambda/callback. The composition-based variant from the final exercise of 04-11:
// The template fixes the flow (connection, statement, loop, cleanup, exceptions);
// you only contribute the variable step: how to map a row.
List<Order> orders = jdbcTemplate.query(
"SELECT * FROM orders WHERE status = ?",
(row, num) -> new Order(row.getLong("id"), row.getString("status")), // your "hook"
"OUT_FOR_DELIVERY");- Observer in the events —
ApplicationEventPublisher.publishEvent(...)+@EventListener: broadcast to anonymous interested parties, identical in intent to PideYa'sOrderObserver(with a bonus:@TransactionalEventListenerto listen "once the commit is real"). - Strategy institutionalized — injecting an interface with N implementations (an autowired
List<DiscountRule>, or choosing with@Qualifier) is Strategy with the container as the selector. - Internal Adapters and Facades — Spring MVC's
HandlerAdapters adapt heterogeneous controller types;DispatcherServletis the facade over all web processing.
Other libraries and frameworks
- Hibernate / JPA:
Session/EntityManageras a Facade over the persistence engine; a virtual Proxy in lazy loading (you touchorder.getCustomer()and a proxy loads the entity right then — and throwsLazyInitializationExceptionoutside a session: the price of the proxy);SessionFactoryis a factory down to its name; the first-level cache works like an Identity Map with a Flyweight flavor: same row, same instance within the session. - JUnit: the
@BeforeEach→ test →@AfterEachcycle is Template Method (in JUnit 3 it was literal: you extendedTestCaseand overrodesetUp()); the runner'sTestWatcher/listeners are Observer;@ParameterizedTestannotations with their argument providers, factories. - Android:
View/ViewGroupis Swing's Composite reincarnated;OnClickListeneris Observer;LayoutInflater.from(context)andFragment.newInstance(...)are factories;RecyclerView.Adapteris an Adapter between your data and the views; observableLiveData/Floweverywhere. - Frontend (for the polyglot glance): the Observer pattern dominates — React hooks and RxJS observables are variations on "subscribe to changes"; Redux combines Command (actions as objects) with a single state tree. GoF patterns are OO design patterns, but their intents cross languages.
How to recognize patterns when reading someone else's code
The practical skill: you open an unfamiliar repository and want to get your bearings. Clues in order of reliability:
- The signature before the name. A constructor that receives an object of its own supertype → Decorator/Proxy/Adapter (check whether it adds, controls or translates). A method that receives a single-method "do something" interface → Strategy/callback. Statics returning their own type → factory. Chainables +
build()→ Builder. - The package structure. An interface with many sibling implementations (
DeliveryFeeCalculation+DistanceFee+FlatRateFee...) screams Strategy or State; an interface + a singleXxxImplimplementation screams... something else (we'll see it in anti-patterns). - The name's suffix — the fastest clue and the least reliable (next section).
- Runtime/debug behavior. Does the stack trace show
$Proxy42orEnhancerBySpringCGLIBclasses? Dynamic proxy. Does the object you traverse never expose its internal collection? Iterator doing its job. - The class documentation. Serious libraries name the pattern in the Javadoc ("This class implements the builder pattern") — the shared vocabulary from 01-01 working exactly as promised.
Cross-check rule: the name gives you the hypothesis; the signature and behavior confirm it. An XxxFactory whose only method is a static getInstance() may be a Singleton in disguise; an XxxManager can be anything, including a problem.
What naming conventions teach
| Suffix / prefix | Pattern hypothesis | Real examples |
|---|---|---|
*Factory, *Supplier, of/valueOf/newXxx |
Factory Method / Abstract Factory | SessionFactory, ThreadFactory, List.of |
*Builder |
Builder | StringBuilder, UriComponentsBuilder |
*Adapter |
Adapter | RecyclerView.Adapter, HandlerAdapter |
*Proxy |
Proxy | java.lang.reflect.Proxy |
*Template |
Template Method (often with callbacks) | JdbcTemplate, RestTemplate |
*Listener, *Observer, on*/add*Listener, subscribe |
Observer | ActionListener, @EventListener |
*Strategy, *Policy, *Resolver, Comparator |
Strategy | RetryPolicy, ViewResolver |
*Handler + a next/successor field |
Chain of Responsibility | servlet filters, pipelines |
*Command, *Action, *Task |
Command | Redux actions, Runnable as a minimal command |
*Visitor, accept/visitXxx methods |
Visitor | FileVisitor, ASM and AST visitors |
Abstract*, Base* |
Likely Template Method | AbstractList |
*Facade, *Service (sometimes), *Helper (one hopes) |
Facade | EntityManager in spirit |
Three lessons this table leaves behind:
- The shared vocabulary works in both directions. Naming your class
PayPalAdapteris not pedantry: it is free documentation that any reader who knows the catalog deciphers in a second. It is the communication advantage from 01-06, cashed in every day. - The name is a contract. If you call something
OrderFactoryand it also sends emails, you are lying to the reader with the worst kind of lie: the kind that looks like documentation. Name after the pattern only when you honor its intent. - The absence of a suffix is also information. The modern JDK prefers
List.oftoListFactory.create: when the pattern is idiomatic, the method name is enough. Suffixes abound where the pattern needs signposting — and are redundant where it is already culture.
Common Mistakes and Tips
- Trusting the name alone.
Context,Manager,Helper,Utilare not patterns; and an*Factorymay not be one either. Hypothesis by name, confirmation by signature. - Seeing patterns where there is only structural coincidence. One class wrapping another doesn't make it a Decorator: ask about the intent (does it add responsibilities while preserving the interface, or control access, or translate?). The head-to-head of the four wrappers applies to reading other people's code too.
- Imitating the form without the why. Copying a library's
getInstance()without inheriting its problem (genuine uniqueness) imports the cost without the benefit. Libraries drag historical decisions too:java.util.Observableis deprecated — even the JDK un-applies badly placed patterns. - Tip: when you discover a pattern in a library, read its source code (it's one click away in the IDE). Watching
BufferedReaderdelegate to its innerReaderteaches more Decorator than any diagram. - Tip: in your own code, be generous with pattern names when the intent is genuine — you hand the next reader the map you had to reconstruct yourself.
Exercises
Exercise 1: pattern safari
Identify the pattern (and the clue that gives it away) in each real fragment:
HttpRequest.newBuilder().uri(uri).timeout(Duration.ofSeconds(5)).GET().build()Collections.unmodifiableList(orders)— returns aListthat throws onadd.Runtime.getRuntime()new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8))Files.walkFileTree(start, new SimpleFileVisitor<Path>() { ... })
Exercise 2: the misleading clue
org.springframework.core.io.ResourceLoader has a single method: Resource getResource(String location), and depending on the prefix (classpath:, file:, https:) it returns a different implementation of Resource. Its name says "Loader". (a) Which pattern is it really, and which clue outweighs the name? (b) Which equivalent piece did we build in PideYa in module 2?
Exercise 3: name audit in PideYa
Go over these course names in your head and say, for each one, what the name promises the reader and whether it honors the pattern's intent: CheckoutFacade, NotifierRegistry, RetryNotifier, DispatchCenter. Which of the four is the only one whose name does NOT announce its pattern, and why is that a reasonable decision?
Solutions
Solution 1: (1) Builder — chainables + build(). (2) Protection Proxy (a wrapper with the same interface that controls access: it doesn't add behavior, it restricts it — that is what separates it from Decorator). (3) The JDK's classic Singleton — a static getXxx() returning the single instance. (4) Decorator on top of Adapter: OutputStreamWriter adapts bytes→characters, PrintWriter decorates with println and formatting — the java.io stack. (5) Visitor (with Template Method thrown in: SimpleFileVisitor provides default implementations you override) traversing a Composite: the directory tree.
Solution 2: (a) Factory Method: the client asks for "a resource for this location" and the implementation decides the concrete class (ClassPathResource, FileSystemResource, UrlResource). The decisive clue is the signature and the behavior — a method returning an abstraction while choosing the subtype for you — not the Loader suffix. (b) The NotifierRegistry from 02-03: same idea, key (channel / prefix) → registered creator.
Solution 3: CheckoutFacade promises Facade and delivers (a single point orchestrating a subsystem). NotifierRegistry promises a registry of factories and delivers (key → Supplier). RetryNotifier doesn't say "Decorator"... and it is the reasonable exception: it names what it contributes (retries) on top of the interface it preserves (Notifier), which is exactly how decorators read (BufferedReader isn't called ReaderDecorator either); the pattern gives itself away through the signature — it receives and exposes a Notifier. DispatchCenter doesn't say "Mediator" either, but "center" communicates the star topology better than the technical name would. The moral: the name must serve the reader; sometimes the domain intent communicates more than the catalog label — as long as the signature confirms the pattern.
Conclusion
The catalog stopped being a book from 1994 and became the blueprint of the tools you use daily: the JDK with its factories, builders, decorators and iterators; Spring as an industrial assembly of Singleton-per-container, proxies and templates; Hibernate, JUnit and Android repeating the same intents. And you take away the reading method: hypothesis from the name, confirmation from the signature and the behavior — with naming conventions as the shared language your own code should speak too. One inverse question remains: your existing code, the code that wasn't born with patterns — how do you carry it toward them without breaking it? Tests as the net, small steps and a catalog of transformations: Refactoring with Design Patterns.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
