Pattern-based design, frameworks, and anti-patterns
◈ 9 cardsA pattern as a three-part rule over context, problem and forces, with consequences; the abstraction ladder from architectural pattern to idiom; frameworks versus patterns; and why an anti-pattern is not simply a bad design.
A pattern is a three-part rule, not a code snippet
A design pattern is a three-part rule expressing a relation between a context, a problem, and a solution. The context describes the environment the problem lives in, so a reader can tell whether their situation is the same situation. The problem is never bare: it comes with a system of forces — the requirements, limits and constraints that pull the solution in different directions, often contradicting one another. It is the forces, not the problem statement, that decide which solution is right, because most problems have several solutions and only some of them survive the forces. And every solution has consequences, which may themselves become forces on the next problem you solve in the same system. A pattern description that omits forces and consequences is a recipe, not a pattern, and it will get applied in situations where it does harm.
An effective pattern does five things: it captures a specific solution to a bounded problem, the solution is proven in practice rather than proposed, the approach is not obvious (an obvious solution needs no name), it identifies its relationships to other design elements, and it is elegant — it earns its complexity.
The abstraction ladder
Patterns span a wide range of scope. Architectural patterns address broad structural problems across a system. Data patterns address recurring data-modelling problems. Component (design) patterns address subsystems and components, how they communicate, and where they sit in a larger architecture. Interface design patterns address recurring user-interface problems, with the characteristics of end users among their forces. There are also WebApp and mobility patterns for those domains. At the lowest level an idiom describes how to implement something in one specific programming language.
Two more distinctions matter. A non-generative pattern merely describes a recurring phenomenon and its context — it names something without telling you what to do. A generative pattern gives a way to build the thing within its system of forces; generativity is the effect where applying several small patterns in succession makes a larger solution emerge that no single pattern described. The GoF catalogue sorts object-oriented patterns into three families: creational (how objects are made and how creation is constrained), structural (how classes and objects are composed into larger structures), and behavioural (how responsibility is assigned and how objects communicate).
A framework is not a pattern
A framework is a reusable skeletal mini-architecture with plug points — hooks and slots — that you fill in with problem-specific classes. The differences are worth memorising because they are a cheap short-answer: a pattern is a description you apply and is generally code-independent, while a framework is embodied in code; a framework usually encompasses several patterns and is therefore larger; a pattern can be applied in any domain where its problem occurs, whereas a framework lives inside one application domain; and a framework works best when adopted unchanged, extended only through its plug points.
How you actually design with patterns
Start from the big picture, because context is what patterns are indexed by. Read the requirements model, build a hierarchy of problems from broad to narrow, and for each one isolate the problem, its context and its forces. Look for a pattern language for the domain — a set of patterns described in a common template and cross-linked to show how they collaborate. If none exists at that specificity, partition the problem into generic domains and search those. Take the broadest problem first, see whether an architectural pattern fits, and if it does, examine everything it collaborates with before adopting it. Then work inward to subsystem and component problems, and finally to interface problems. A pattern-organizing table — problem statements down the side, pattern types across the top, candidate names in the cells — keeps the search manageable. Before adopting anything, compare your context and forces to the pattern's; and refine the result against ordinary design quality criteria afterwards.
The common mistake has a predictable shape: not enough time spent understanding the problem, a pattern chosen because it looks right, and then a refusal to admit the mismatch, so the design is bent to fit the pattern. If the context and the forces are wrong, the pattern is wrong, however famous it is.
Anti-patterns
An anti-pattern describes a commonly used solution to a design problem that reliably has negative effects on quality — and for which a better, known refactoring exists. The word 'commonly' is the load-bearing one. Bad designs are usually unique and there is nothing to say about them in general; an anti-pattern is worth naming precisely because it is the path of least resistance, it looks reasonable while you are taking it, and teams keep arriving at it independently. Anti-patterns are categorised as analysis, software design, architectural, development and management anti-patterns, and they are described the other way round from patterns: a pattern description starts from a recurring solution and adds forces, symptoms and context, whereas an anti-pattern description starts from a recurring problem or bad practice, lists its symptoms and negative consequences, and then gives the procedure for reversing it. That makes them useful in two places — as a refactoring guide, and as a checklist for technical reviews.
Worked example — one pattern and one anti-pattern in BorrowBox
The pattern. Context: a member's reservation is displayed simultaneously in the app's home card, the locker-bank kiosk and the librarian's queue, and all three must show the same state within a second of it changing. Forces: the views must stay current; the reservation must not know about user-interface classes, because the kiosk is a separate deployment on a different release cycle; and adding a fourth view later must not modify the reservation. Solution: the Observer arrangement — the reservation is a subject that publishes state changes to registered observers that it knows only by interface. Consequence, stated honestly: notification order is not defined, so the audit log must not infer sequence from the order in which observers fired; we timestamp at the subject instead.
The anti-pattern. BorrowBox's first release had a single BorrowBoxService class that reserved tools, took payments, opened lockers and sent email. That is a God Object: it appeared as the obvious place to put each new behaviour, every feature added a method to it, and within four months it had no cohesion, every change touched it, every branch conflicted in it, and it could not be tested without standing up a payment sandbox and a locker simulator. Nobody chose it — everybody chose the next small increment of it, which is exactly why it needed a name. The known refactoring is the one the anti-pattern prescribes: move behaviour out to the objects that own the data, splitting along use-case boundaries into reservation, billing, locker-control and notification components, and reduce the survivor until it coordinates rather than computes.