Once you have been doing object-oriented programming for a while, you start to notice that certain problems come up again and again: you need exactly one instance of something to exist, you want to create objects without coupling yourself to their concrete classes, or you need several components to react when something changes. Design patterns are precisely that: proven, named solutions to recurring software design problems. In this lesson you will learn what a design pattern actually is, which elements it is made of, what things are not a pattern (a very common source of confusion), and you will see a first real problem that will serve as motivation. You will also meet PideYa, the project that will accompany us throughout the entire course.
Contents
- The course project: PideYa
- Definition of a design pattern
- The four essential elements: context, problem, solution and consequences
- What a design pattern is NOT
- How a pattern is described: the pattern template
- A first motivating look: a real problem in PideYa
The course project: PideYa
Throughout the whole course we will work on a single project: PideYa, a fictional food-delivery ordering platform. The idea is that you do not learn the patterns through abstract ClassA and ClassB examples, but by applying them to a realistic domain that we will grow module by module.
PideYa has the typical ingredients of this kind of application:
- Restaurants with their menu of dishes, prices and opening hours.
- Customers who browse the menus, build a cart and place orders.
- Payments through different gateways (card, PayPal, in-app wallet...).
- Couriers who pick up and deliver the orders.
- Notifications to customers and couriers (email, SMS, push) as the order progresses.
This domain is a gold mine for design patterns: there are complex objects to build (an order with its lines, discounts and delivery address), families of interchangeable objects (payment gateways, notification channels), states that change (an order goes from received to preparing, out for delivery and delivered), and components that need to know what others are doing without becoming coupled to each other. Whenever an example allows it, we will frame it in terms of PideYa.
flowchart LR
C[Customer] -->|builds cart| P[Order]
P -->|pays through| PG[Payment gateway]
P -->|is assigned to| R[Courier]
P -->|generates| N[Notifications]
Rest[Restaurant] -->|prepares| P
You do not need to memorize anything about PideYa right now: we will introduce each piece as we need it.
Definition of a design pattern
A practical and widely accepted definition is the following:
A design pattern is a general, reusable, named solution to a design problem that appears recurrently in a given context of object-oriented software development.
Let's unpack this definition, because every word matters:
- General solution: the pattern does not solve your specific problem, but the family of problems yours belongs to. That is why it always has to be adapted.
- Reusable: it has been applied successfully many times, in many projects. A trick that only worked once is not a pattern; a pattern is distilled from collective experience.
- Named: this matters more than it seems. Saying "we use an Observer here" communicates in two words a complete structure of classes, responsibilities and collaborations. Patterns create a shared vocabulary among developers.
- Recurring problem: if the problem only appears in your project, the solution may be good, but it is not a pattern.
- In a given context: no pattern is good or bad in the abstract. A pattern is appropriate in its context and counterproductive outside of it.
A useful analogy: a design pattern is to software what a classic recipe is to cooking. The recipe for a Spanish potato omelette tells you the ingredients, the steps and the expected results, but every cook adapts it (with onion or without?). Nobody "copies and pastes" an omelette: they cook it following the pattern.
The four essential elements: context, problem, solution and consequences
Every design pattern, whichever it is, is built on four elements. If any of them is missing, you do not have a complete pattern — you only have a fragment of an idea.
| Element | Question it answers | Informal example |
|---|---|---|
| Context | In what situation does the problem appear? | "An application with several notification channels that may grow in the future" |
| Problem | What conflict or opposing force must be resolved? | "The code that sends notifications should not change every time we add a new channel" |
| Solution | What structure of classes/objects and collaborations solves it? | "Define a common abstraction and make the sender depend only on it" |
| Consequences | What do we gain and what do we pay by applying it? | "We gain extensibility; we pay with more classes and indirection" |
Two of them deserve a closer look:
The "forces" of the problem
The problem a pattern addresses is never trivial in the sense of "I want to add two numbers". There are always forces in tension: requirements pulling in opposite directions. For example: I want the flexibility to change behavior at runtime (pulling toward more indirection) versus I want simple, direct code (pulling toward fewer classes). The pattern is a documented balance between those forces.
The consequences: there is no free pattern
This point separates the professional from the amateur: every pattern has a cost. More classes, more indirection, more concepts the next developer has to understand. A well-applied pattern is one whose benefit clearly outweighs its cost in that context. We will devote the lesson Advantages and Disadvantages of Using Design Patterns to analyzing this in detail.
What a design pattern is NOT
This is where the most frequent misunderstandings concentrate. Let's clear up the three main ones:
A pattern is NOT copy-pastable code
There is no such thing as "the code of the Observer pattern" that you can paste into your project. A pattern describes a structure and a set of responsibilities; the concrete code is something you write each time, adapted to your domain, your language and your constraints. Two correct implementations of the same pattern may not share a single identical line. When you see Java code for a pattern in this course, understand it as one possible materialization, not as the official implementation.
A pattern is NOT a framework or a library
A framework (Spring, Hibernate...) is concrete software that you run; a pattern is knowledge that you apply. The real relationship is the other way around: frameworks are built with patterns. Spring, for example, uses dozens of them internally, which is why understanding patterns makes you a much better framework user: you stop seeing "magic" and start seeing familiar structures.
| Design pattern | Framework / library | |
|---|---|---|
| Nature | Knowledge, description | Executable code |
| How it is used | Adapted and implemented each time | Imported and invoked |
| Dependency | None: it lives in your head and in your design | Your project depends on it |
| Level | Design (micro-architecture of classes) | Implementation |
A pattern is NOT an algorithm
An algorithm (quicksort, Dijkstra...) solves a computational problem: given an input, it produces an output in a certain number of steps, and its correctness can be proven. A pattern solves a code organization problem: how to distribute responsibilities among classes so the system stays maintainable and extensible. An algorithm is measured in efficiency (time, memory); a pattern is measured in design qualities (coupling, cohesion, extensibility).
How a pattern is described: the pattern template
Patterns are documented in catalogs, and each pattern is presented using a standardized template. The classic structure (inherited from the Gang of Four catalog, whose history we will cover in the next lesson) includes these sections:
- Name: short and evocative. It is the piece of vocabulary you will share with your team.
- Intent: one or two sentences stating what the pattern achieves.
- Also known as: other names it circulates under.
- Motivation: a concrete scenario where the problem appears and the pattern solves it.
- Applicability: signals for when to use it (and, by omission, when not to).
- Structure: a class diagram with the participants. In this course we will draw them with mermaid, and in the lesson Essential UML for Understanding Patterns you will learn to read them.
- Participants: each class/interface in the diagram and its responsibility.
- Collaborations: how the participants interact at runtime.
- Consequences: benefits and costs.
- Implementation: tips, variants and pitfalls when turning it into code.
- Sample code: a materialization in a concrete language.
- Known uses: where it has been applied in real systems.
- Related patterns: alternatives and common combinations.
In this course we will use a lightweight version of this template for each pattern in modules 2, 3 and 4: intent, problem in PideYa, structure in mermaid, implementation in Java, consequences and related patterns. Get used to this structure: reading patterns "in template form" is a skill in itself, because professional catalogs (books, company wikis) use this format.
A first motivating look: a real problem in PideYa
Let's see why all of this matters, with a real problem from PideYa. Note: here we are only going to feel the pain of the problem; the solutions, with their proper names, will arrive in their corresponding lessons.
When an order changes status (for example, the restaurant marks it as "preparing"), several parties need to be informed: the customer via push notification, the assigned courier, and the internal statistics dashboard. A first naive version might look like this:
public class Order {
private String status;
public void changeStatus(String newStatus) {
this.status = newStatus;
// Notify the customer via push
PushService push = new PushService();
push.send(this.getCustomer().getDeviceToken(),
"Your order is: " + newStatus);
// Notify the courier via SMS
SmsService sms = new SmsService();
sms.send(this.getCourier().getPhone(),
"Order " + this.getId() + ": " + newStatus);
// Update statistics
StatsDashboard dashboard = new StatsDashboard();
dashboard.recordStatusChange(this.getId(), newStatus);
}
}Let's explain what this code does and why, even though it works, it is a problem waiting to grow:
- The
changeStatusmethod updates the status and then itself creates and calls every interested service: push, SMS and statistics. - The
Orderclass, which should be concerned with the business logic of an order, knows the details of three systems that are none of its business (how a push is sent, what phone number the courier has, that a statistics dashboard even exists).
Now imagine the typical evolution of the product:
- Marketing asks for an email to be sent as well in certain statuses. →
Orderhas to be touched. - A loyalty program is added that awards points on delivery. →
Orderhas to be touched. - In the unit tests for
Order, every test sends real SMS messages (and they cost money!) because the services are created withnewinside the method. → Hard to test. - The courier may not be assigned yet →
NullPointerExceptionin production.
The underlying problem, in pattern terms, would be:
- Context: a business object whose changes interest a variable number of third parties.
- Problem: the object should not know its interested parties, nor change every time a new one appears; the forces in tension are notify everyone versus be coupled to no one.
- Solution: there is a classic structure that solves exactly this... and it has a name. We will study it in module 4 (Observer). You will also see that creating those services with
newis squarely the territory of the creational patterns in module 2. - Consequences: we will analyze them there, because that solution is not free either.
This is what the course will give you: the ability to recognize these situations, put a name to them and apply the solution distilled by thousands of developers before you, knowing its price.
Common Mistakes and Tips
- Believing that learning patterns means memorizing diagrams. What matters is recognizing the problem and its forces; the structure can be deduced (or looked up) afterwards. If you memorize the solution without the problem, you will apply patterns where they do not belong.
- Searching for "the pattern's code" to copy it. It does not exist. There are examples, like the ones in this course, that you must adapt to your domain. If you copy without adapting, you will end up with generic names (
Manager,Handler) that say nothing about your business. - Confusing a pattern with a framework. If someone says "we use the Spring pattern", concepts are being mixed up. Spring uses patterns; it is not a pattern.
- Wanting to apply a pattern as soon as possible. The urge to "put patterns in" as soon as you learn them is almost universal (and dangerous). First master the problem each one solves; lesson 01-06 and module 5 will give you criteria for deciding.
- Tip: when you read someone else's code (or a framework), practice putting names to the structures you recognize. It is the best possible training and dramatically speeds up code reading.
Exercises
Exercise 1: context, problem, solution and consequences
Think about the following PideYa situation and write out the four elements of a possible pattern (without naming any specific pattern, even if you know one): "The PideYa mobile app must work with three different payment gateways (card, PayPal and in-app wallet), and the sales team is already negotiating with a fourth."
Exercise 2: pattern or not a pattern?
For each item, state whether it can be considered a design pattern and why or why not:
- The quicksort sorting algorithm.
- The
java.util.logginglibrary. - "When several classes share a behavior that varies, extract it into its own hierarchy and inject it, instead of inheriting."
- A
Utils.javafile with static methods that your team copies from project to project.
Exercise 3: spotting the forces
Reread the Order.changeStatus(...) code from this lesson. List at least three "forces" or requirements in tension that make that design problematic, expressing them as pairs of the form "we want X, but we also want Y".
Solutions
Solution 1 (one possible write-up):
- Context: an ordering application that charges its customers through external payment providers, whose number and variety grows with the business.
- Problem: the charging code must not be rewritten for every new gateway; each gateway has its own API, but the ordering process should treat them uniformly. Forces: uniformity for the code that charges versus the real diversity of the APIs.
- Solution (sketch): define a common "payment" abstraction that the ordering process depends on, and encapsulate the particulars of each provider behind that abstraction, so that adding a gateway means adding a new piece, not modifying the existing ones.
- Consequences: we gain extensibility and the ability to test the charging logic without real gateways; we pay with one more layer of indirection and with the effort of designing an abstraction that fits very different APIs.
Solution 2:
- No. It is an algorithm: it solves a computational problem with a defined input and output, and it is evaluated by efficiency, not by design qualities.
- No. It is a library: concrete code that you import and run. (Internally it applies patterns, but it is not one itself.)
- Yes (or at least it has the shape of one): it describes a recurring problem, a general solution in terms of structure and responsibilities, and it is adaptable to many contexts. In fact, it points to a principle we will see in 01-03: favor composition over inheritance.
- No. It is copy-pastable code, exactly what a pattern is not. It may be useful, but it describes no problem, context or consequences: it is just a concrete implementation that travels between projects.
Solution 3 (three valid examples; there are more):
- We want every interested party to learn about the status change, but we also want
Orderto know none of them. - We want to add new notifications (email, loyalty) easily, but we also want to avoid modifying
Ordereach time. - We want to test
Orderwith unit tests, but we also want production to use the real SMS and push services (and withnewinside the method, there is no way to substitute them in tests).
Conclusion
In this lesson we have laid the foundations of the course: a design pattern is a named, proven and adaptable solution to a recurring problem, always described through four elements (context, problem, solution and consequences) and documented in standardized templates. Just as important is what a pattern is not: not copy-pastable code, not a framework, not an algorithm. And with the PideYa notification problem you have already seen the kind of situation patterns solve: code that works today but degrades with every new requirement.
You may be wondering where this idea of cataloging named solutions came from. The answer is a curious story that begins, of all places, in building architecture. That is the subject of the next lesson: History and Origin of 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
