Chapter 38: Design Patterns in Python
Section 1
A design pattern is a reusable solution to a recurring design problem, described in general terms so that it can be applied in many different programs, rather than a piece of code that can be copied unchanged from one program to another. Patterns arise when many programmers, working independently, discover similar solutions to similar problems, and when those solutions are then named, described, and collected, so that other programmers can recognise the problem, recall the solution, and apply it, adapted to their own context, without rediscovering it from scratch. Learning patterns therefore gives you a vocabulary: when a colleague says that a problem calls for a factory, or an observer, or a strategy, you know roughly what structure they mean, what problem it solves, and what trade-offs it brings, which makes design discussions faster and clearer, with its roles, its relationships, and its consequences, that would otherwise take paragraphs to describe, from scratch, in every discussion, about every design, that uses that structure, in practice, for real programs, with real design problems, over time.
Patterns are not rules, and they are not a checklist to apply to every program. A pattern solves a specific problem, in a specific context, with specific forces pulling in different directions, making code harder to read, harder to test, and harder to change, for no gain, in flexibility, or in maintainability, that the program actually needs, in practice, for its real requirements, and its real users, over time. The skill of using patterns well is therefore not memorising as many patterns as possible, understanding the pattern's consequences, including its costs, and deciding, deliberately, whether those consequences are acceptable, for the program at hand, compared with a simpler solution, that may serve, just as well, for the program's current needs, without the pattern's complexity, which should be preferred, whenever it suffices, because simplicity, as discussed throughout this book, is a value, in its own right, not merely a fallback, when no pattern applies, to a given design problem, in a given program, at a given time, during its development, or its maintenance, generally.
Patterns in Python also differ, in some respects, from patterns as originally described for other languages, because Python's features, such as first-class functions, dynamic typing, and built-in data structures, make some patterns unnecessary, or make them expressible, far more simply, than in languages, without those features. A pattern that requires elaborate class hierarchies, in another language, may reduce, in Python, to passing a function, as an argument, or to using a built-in structure, in a straightforward way, without any special pattern machinery, at all, which is a good outcome, because the simplest expression, of a solution, is usually, the best one, for readability, and for maintainability, over time. Throughout this chapter, you will therefore learn patterns, as they apply, in Python, specifically, with attention, to where Python's features, simplify them, where they remain, valuable, in their fuller form, and where a simpler, Pythonic approach, serves better, than a literal, textbook implementation, imported, unchanged, from another language, whose constraints, do not apply, to Python programs, written idiomatically, for real users, with real requirements, in practice, generally, and specifically, for each pattern, covered here, in turn, over the course, of this chapter, as a whole.
Check: Why is a design pattern a reusable solution description rather than code to copy unchanged?
Section 2
Creational patterns deal with how objects are created, in the code, that needs it, at the moment, for its purpose, in the program, as a whole, over time. Sometimes, the exact class, to create, depends on input, or on configuration, at run time, needing the object, in detail, because that knowledge, would couple it, tightly, making it, harder to change, is added, or an old one, is removed, from the program, over time, as requirements evolve, and as new kinds, of objects, appear, that must be created, through the same, creation logic, without changing, every place, that creates them, individually, which would be, error-prone, and tedious, in a large program, with many creation sites, scattered, across many files, and many modules, that a human, may miss, in a manual search, however careful, they may be, in their attempt, to find them all, before declaring, a change, complete, and safe, to merge, into shared code, will then create, the wrong object, or fail, at run time, when it encounters, a new kind, to handle, correctly, in practice, generally.
Other creational problems, include, creating, a complex object, step by step, where a single constructor, with many parameters, would be, confusing, and error-prone, for callers, who must supply, all parameters, in the right order, without forgetting, any, or supplying, of bugs, in constructors, with many arguments, that are hard, to remember, in order, and that are easy, to confuse, with each other, especially, when several parameters, share the same type, such as, several text values, or several numbers, that look alike, in a call, without names, to distinguish them, clearly, for a reader, or for a caller, writing the call, from memory, or from documentation, that may be, incomplete, or outdated, as discussed, for documentation, generally, where stale documentation, misleads readers, about an interface, that has changed, since the documentation, was written, and that now, behaves differently, than documented, in ways, that cause, failures, or wrong results, for callers, who trusted, the documentation, over the code, itself, in practice, generally.
Creational patterns, solve, these problems, by separating, the knowledge, of how to create, an object, from the code, that uses the object, so that creation logic, lives in one place, centrally, and where new kinds, of objects, can be added, without changing, every user, of those objects, individually, across the program, as a whole, over time, generally. That separation, is the common thread, running through, all creational patterns, covered, despite their differences, in detail, and in the specific, creation problems, they solve, for different situations, with different constraints, on flexibility, complexity, and performance, that must be weighed, when choosing, among them, for a particular program, with particular requirements, and particular users, over time, generally. As you learn, each creational pattern, ask, first, what creation problem, it solves, second, whether your program, actually has, that problem, in a form, that justifies, the pattern's complexity, and third, whether a simpler, Pythonic approach, such as, a function, that creates, and returns, an object, based on a parameter, would serve, just as well, which, as noted, in the introduction, should be preferred, whenever it suffices, in practice, generally, and specifically, for each creation problem, that arises, during development, or maintenance, of the program, as a whole, over time.
Check: What common idea links all creational patterns, despite their differences?
Section 3
The singleton pattern ensures, that a class, and provides, a global point, of access, to that instance, of a program, rather than, each creating, their own, separate instance, with separate state, that could diverge, from each other, over time, causing, inconsistencies, between parts, of the program, that assume, they are sharing, the same state, but that are, in fact, using different objects, with different state, because each part, independently, without knowing, that other parts, during execution, with real data, and real users, for correct behaviour, generally, and specifically, for shared resources, such as, a configuration object, where a single, shared instance, makes sense, as a design, for the program, as a whole, over its run, with real workloads, and real requirements, for consistency, across its parts, generally.
Singletons, should be used, sparingly, however, because they introduce, global state, which makes code, harder to test, over time, as discussed, for global state, generally, depends, not only, on its arguments, but also, on global state, changed, by other parts, of the program, at unpredictable times, during execution, making tests, order-dependent, and making reasoning, about a function, require knowledge, global state, at that moment, rather than, alone, in readability, and in testability, when the benefit, genuinely outweighs it, for a particular, shared resource, whose singularity, rather than, a convenience, adopted, to avoid, passing an object, explicitly, between parts, of the program, that need it, which is often, the real motivation, behind a singleton, in practice, where passing, an object, through many layers, feels tedious, feels like, an easy shortcut, that saves, plumbing, at the cost, of hidden coupling, to global state, that will make, future changes, harder, and future tests, more fragile, over time, generally, and specifically, for programs, that grow, beyond their initial, simple design, into larger systems, that share, global state, through singletons, in ways, that become, increasingly hard, to untangle, grows, in scope, and in complexity, over time.
In Python, a singleton, can be implemented, in several ways, including, through a module, which is itself, a singleton, in practice, because modules, are loaded, once, and shared, across a program, which is often, the simplest, Pythonic way, to provide, a single shared object, without any special, singleton machinery, at all, simply, by defining, the shared object, at module level, and importing it, wherever it is needed, throughout the program, as a whole, over its run, generally. That module approach, is usually, preferable, to a class-based singleton, with elaborate, instance-control logic, because it is, simpler, clearer, and more idiomatic, in Python, where modules, are a natural, unit of sharing, for program-wide services, such as, configuration, or logging, that need, a single instance, for the program, as a whole, over its run, with real users, and real workloads, generally. Whatever, implementation, you choose, document, clearly, that the object, is intended, to be single, and why, so that future maintainers, do not accidentally, create, additional instances, or replace, the singleton, with a design, that breaks, the sharing, assumption, on which, other parts, of the program, depend, for correct behaviour, with real data, and real state, shared, across the program, as a whole, over time, generally, and specifically, for each singleton, in a program, over its lifetime, as it evolves, through maintenance, and refactoring, generally.
Check: Why should singletons be used sparingly despite their convenience?
Section 4
The factory method pattern, defines, an interface, for creating, an object, but lets, subclasses, decide, which class, to instantiate, so that, the code, that needs, an object, to know, in advance, but can, rely on, to provide, based on, input, or configuration, can interpret, needing, itself, in detail, through that factory, over time, as new kinds, are added, or old kinds, are removed, from the program, during its evolution, and during, its maintenance, by developers, who must, update creation logic, centrally, in the factory, rather than, scattered, across the program, where objects, are created, individually, would cause, a wrong object, to be created, or a failure, at run time, is encountered, was not updated, to handle, correctly, in practice, generally, and specifically, with many sites, and many kinds, of objects, created, through them, over time, generally.
In Python, a factory, is often, simply, a function, that takes, a parameter, describing, what kind, of object, is needed, and returns, an instance, for that parameter, without any, elaborate class hierarchy, for the factory, itself, more Pythonic expression, where the factory, is a method, that subclasses, override, to change, which product, is created, for their particular, variant, of the creator, in a hierarchy, of creators, and products, that mirror, each other, in structure, maintained, together, as new variants, are added, to either side, of the hierarchy, over time, and during, its maintenance, by developers, who must, keep both hierarchies, in sync, in complexity, that a simple, function factory, avoids, by centralising, creation logic, in one function, that can be, extended, without creating, a new subclass, of creator, and product, that grows, in two dimensions, at once, as new kinds, are added, over time, generally, and specifically, for programs, where creation logic, is relatively simple, suffices, to express it, clearly, and maintainably, for all kinds, currently supported, by the program, as a whole, over its run, with real inputs, and real configuration, generally.
Use, a factory, when, the exact class, to create, depends, on run-time information, and when, the code, needing the object, should not, be coupled, to that decision, in detail, because, that coupling, would make, the code, harder to change, when new kinds, are added, or when, the decision rule, changes, for existing kinds, in ways, that should be, reflected, centrally, in the factory, rather than, individually, across the program, as a whole, over time, generally. Do not, use a factory, when, the class, to create, is known, statically, and when, calling its constructor, directly, is clear, and simple, because, adding a factory, in that case, merely, adds indirection, without benefit, forcing readers, to look up, the factory, to understand, what object, is actually created, which interrupts, reading, and which, for a simple, static creation, in flexibility, or in maintainability, over direct construction, which states, plainly, what is created, itself, without indirection, for a reader, to follow, or method, that must be, read, and understood, in addition, itself, to know, what object, is actually created, for that call, in practice, generally, and specifically, in a program, where direct construction, would have sufficed, was added, unnecessarily, out of habit, rather than, out of need, for run-time, creation decisions, that the caller, should not make, itself, in detail, of object, it might need, over time, generally.
Check: When does a simple factory function suffice instead of a full factory method hierarchy?
Section 5
The abstract factory pattern, provides, an interface, for creating, families, without specifying, so that, a program, can work, of objects, that belong, together, such as, without knowing, in advance, which family, and without, individually, switching families, a matter, of changing, many places, are named, directly, would mix, objects, together, causing, inconsistencies, between parts, that assume, they belong, but that, in fact, use objects, that clash, in practice, who experience, the inconsistency, as a confusing, or broken interface, across parts, of the program, that should, belong together, of related objects, used consistently, throughout the program, as a whole, over its run, generally, and specifically, for each family, of objects, that a program, may use, alternatively, depending on, configuration, or user choice, at run time, generally.
An abstract factory, is essentially, a factory, for factories, or more precisely, a single interface, through which, a program, obtains, it needs, by calling, factory methods, on that interface, each of which, returns, one kind, of object, from the family, so that, by choosing, at the start, a program, chooses, an entire family, at once, consistently, without needing, to choose, each kind, individually, at each point, of use, throughout the program, is needed, for its purpose, in the program, as a whole, over time, generally. That consistency, main benefit, over using, separate factories, for each kind, of object, independently, where a program, could accidentally, combine, objects, from different families, by choosing, different factories, for different kinds, at different points, in its code, enforcing, that all choices, belong, with real configuration, that determine, which family, should be used, consistently, across all parts, of the program, for their behaviour, or their appearance, generally, and specifically, for each part, that obtains, objects, rather than, directly, from concrete classes, that would, couple it, prematurely, before the family, is chosen, at run time, by configuration, as appropriate, for the program, in question, generally.
Abstract factories, are powerful, but they, add considerable, complexity, in the form, of multiple interfaces, and multiple implementations, that must be, maintained, together, as new families, or new kinds, of objects, are added, over time, and during, its maintenance, by developers, who must, keep all factories, and all products, in sync, across families, maintenance burden, when a program, genuinely needs, to switch, between families, of related objects, at run time, of its function, rather than, as a hypothetical, future need, that may never, actually arise, in practice, for real users, that determine, which family, to use, for their run, of the program, specifically, and their preferences, generally, over time. For many programs, a simpler approach, such as, passing, a configuration object, that determines, which concrete classes, to use, in a few, central places, or using, for each kind, with a shared, configuration parameter, that ensures, consistency, across kinds, without a full, abstract factory hierarchy, will serve, just as well, with far less, complexity, and far less, maintenance burden, as it evolves, of objects, change, in ways, can accommodate, which should be, reserved, for programs, where family consistency, is a central, recurring problem, that simpler approaches, cannot solve, reliably, without risk, of mixing families, accidentally, in practice, generally, and specifically, for each program, where that risk, is real, and significant, for its users, and its correctness, over time, generally.
Check: What consistency problem does an abstract factory solve that separate factories do not?
Section 6
The builder pattern, separates, the construction, so that, can create, different representations, depending on, are assembled, during construction, or when, its construction, involves, several steps, validation, between steps, cannot express, clearly, or safely, for callers, all parameters, at once, without forgetting, any, or supplying, for constructors, generally, where builder, offers, a clearer, step-by-step alternative, that guides, callers, through construction, one part, that state, means, validated, rather than, together, confusing call, to read, and hard, writing it, from memory, incomplete, or outdated, as discussed, for documentation, generally, misleads readers, was written, behaves differently, than documented, in ways, that cause, failures, or wrong results, for callers, who trusted, the documentation, over the code, itself, in practice, generally.
A builder, typically, provides, methods, for adding, each part, of the object, returning, the builder itself, after each step, so that, calls, can be chained, that states, plainly, part by part, in order, until, a final method, is called, to produce, the completed object, from the parts, accumulated, in the builder, during its steps, that reads, almost like, a description, of the object, itself, rather than, with many, positional arguments, whose meaning, must be inferred, from their order, alone, without names, through them, clearly, and reliably, with many parts, whose order, is hard, to remember, and easy, to confuse, especially, when several parts, such as, several text values, or several numbers, that look alike, without names, to distinguish them, clearly, for a reader, writing the call, from memory, or from documentation, that may be, incomplete, or outdated, as noted above, for constructor calls, generally, specifically, to them, for complex objects, with many parts, that need, step-by-step construction, with validation, between steps, as appropriate, for the object, in question, generally, and specifically, for each part, added, during building, over time, as the object, takes shape, part by part, until complete, and ready, for use, in the program, as a whole, over its run, generally.
Builders, are most valuable, for objects, where a constructor, would need, many parameters, most of which, callers, would leave, at defaults, in most calls, making, constructor calls, long, and confusing, with many, default values, supplied, explicitly, or omitted, through keyword arguments, that are hard, to remember, in full, with many options, that interact, with each other, in ways, that a constructor, cannot validate, until all parameters, have been supplied, together, at once, while a builder, can validate, each part, and can guide, callers, toward valid combinations, that only offer, valid next steps, for the parts, already added, in a sequence, that leads, to a valid, completed object, without requiring, callers, to know, all valid combinations, in advance, from documentation, alone, which may be, incomplete, or outdated, as noted above, for documentation, generally. For simpler objects, with few parts, and few options, a builder, is overkill, adding, for an object, that a constructor, could create, clearly, with a few, named arguments, that state, plainly, without indirection, through a builder, that must be, read, and understood, in addition, to the object, itself, to know, for a given, builder sequence, in practice, generally, and specifically, where direct construction, would have sufficed, was added, unnecessarily, out of habit, rather than, out of need, for step-by-step construction, with validation, between steps, that the object, actually requires, in practice, generally.
Check: When is a builder preferable to a constructor with many parameters?
Section 7
The prototype pattern, creates, new objects, by copying, an existing object, rather than, by constructing, a new one, from scratch, through its constructor, supplied, anew, which is useful, when, creating an object, from scratch, is expensive, or complex, or when, the exact class, of the object, to create, is not known, statically, at the point, of creation, but an example, is available, to copy, which can then, be modified, as needed, without repeating, the expensive, or complex, construction, that produced, the original example, through its constructor, or through, a lengthy, initialisation process, that involved, loading data, or computing, initial state, that would be, wasteful, to repeat, in a program, that creates, many similar objects, over its run, with real data, and real workloads, generally, and specifically, created, by copying, rather than, by fresh construction, from scratch, each time, it is needed, in the program, as a whole, over time, generally.
Copying, however, raises, questions, about depth, that must be, understood, before using, prototype, safely, in a program, copies, but shares, any objects, those attributes, refer to, such as, lists, or other objects, contained, so that, changing a shared, contained object, through the copy, also changes it, through the original, unexpectedly, for code, that assumed, the copy, was independent, of the original, in all respects, that can cause, inconsistencies, between objects, that were meant, to be independent, after copying, but that, in fact, share, through shallow copying, prototype pitfall, for beginners, who assume, that copying, always produces, regardless of depth, for shallow copies, in Python, generally, are distinct operations, with different guarantees, about independence, that must be, chosen, deliberately, based on, whether shared, contained state, is acceptable, or whether, full independence, is required, for correct behaviour, with real data, and real modifications, to copied objects, over time, generally, and specifically, in a program, where copying, is the creation mechanism, for new objects, of a given kind, generally.
A deep copy, by contrast, copies, contained objects, recursively, producing, a fully independent object, whose contained state, does not share, anything, with the original's, contained state, so that, changes, to the copy, cannot affect, the original, in any way, through shared references, which is safer, for independence, but more expensive, in time, and in memory, than a shallow copy, because it must, traverse, and copy, the entire, contained structure, however large, or deeply nested, it may be, for a given object, with given data, at a given time, during execution, with real workloads, and real performance requirements, that must be met, for users, who expect, responsive behaviour, from the program, in question, generally. Choosing, therefore, involves, a trade-off, between safety, and cost, that should be, decided, deliberately, for each prototype use, based on, whether contained state, will be modified, through the copy, and whether, such modification, affecting the original, would be, a bug, or acceptable, shared behaviour, for the program's purpose, in practice, generally, and specifically, for each object, copied, through prototype, in a program, over its run, with real data, and real modifications, to copied objects, that may, or may not, be intended, to affect, the original, in the first place, during creation, through prototype, as a mechanism, for new objects, generally, and specifically, for each prototype, in a program, over time, as a whole.
Check: What is the key difference between a shallow copy and a deep copy for prototype creation?
Section 8
Structural patterns, deal with, how objects, and classes, are composed, into larger structures, that are, flexible, and efficient, to maintain, over time, interact, that a simple, direct composition, cannot handle, cleanly, without creating, tight coupling, between parts, that should, remain independent, for maintainability, and for testability, generally, and specifically, that arises, during development, or maintenance, of a program, as a whole, over time, generally. Where creational patterns, focus, on how objects, are created, structural patterns, focus, on how objects, are combined, once created, into structures, that provide, new functionality, or that adapt, incompatible interfaces, or that simplify, complex subsystems, for their users, who should not, need to understand, every part, of a subsystem, to use it, effectively, for their purpose, in the program, as a whole, over its run, with real data, and real requirements, for simplicity, and for flexibility, in composition, generally, and specifically, covered, despite their differences, in detail, structural problems, they solve, for different situations, with different constraints, on coupling, complexity, and performance, when choosing, among them, with particular requirements, and particular users, over time, generally.
Common structural problems, include, using, a class, whose interface, does not match, what your code, expects, because it was, written, with different conventions, for method names, or parameter orders, that your code, cannot easily change, without modifying, that class, itself, which may be, in a library, that is risky, to change, for fear, of breaking, other users, of that class, throughout the program, as a whole, over time, generally. Another, is adding, responsibilities, to an object, dynamically, without creating, a proliferation, of subclasses, for every combination, of responsibilities, in practice, for real objects, with real combinations, of features, that would require, an exponential number, of subclasses, if each combination, in a hierarchy, that grows, unmanageably, as new responsibilities, are added, over time, and during, its maintenance, by developers, who must, keep that hierarchy, understandable, and maintainable, despite its growth, in size, and in complexity, over time, generally, and specifically, added, to objects, in a program, through structural patterns, that allow, dynamic composition, rather than, static subclassing, for every combination, of features, in practice, generally.
A third structural problem, is simplifying, a complex subsystem, with many parts, and many interactions, between them, into a single, simple interface, that most users, can use, without understanding, in detail, which is valuable, because most users, need only, a small part, for their purpose, and should not, be forced, to learn, effectively, for their work, in the program, as a whole, over its run, with real data, and real requirements, for simplicity, generally. Structural patterns, solve, these problems, by introducing, intermediate objects, or interfaces, that mediate, between parts, that would otherwise, be tightly coupled, directly, together, in a program, as a whole, over time, generally. As you learn, each structural pattern, ask, first, what composition problem, it solves, second, whether your program, actually has, that problem, in a form, that justifies, the pattern's complexity, and third, whether a simpler, Pythonic approach, such as, using a function, would serve, just as well, which, as noted, in the introduction, should be preferred, whenever it suffices, in practice, generally, and specifically, for each structural problem, that arises, during development, or maintenance, of the program, as a whole, over time.
Check: What distinguishes structural patterns from creational patterns in focus?
Section 9
The adapter pattern, converts, the interface, so that, classes, with incompatible interfaces, can work, together, without modifying, either class, itself, which is useful, when, you need, to use, an existing class, whose interface, does not match, what your code, expects, because it was, written, with different conventions, for method names, or parameter orders, that your code, cannot easily change, without modifying, that class, itself, which may be, in a library, that is risky, to change, for fear, of breaking, other users, of that class, throughout the program, as a whole, over time, generally, and specifically, that must be, adapted, expected, by new code, that uses it, for its purpose, in the program, as a whole, over its run, with real data, and real requirements, for compatibility, generally.
An adapter, typically, wraps, the existing class, holding, a reference, to an instance, of it, and providing, methods, with the names, and signatures, that clients expect, each of which, translates, its call, into a call, with the appropriate, method name, and arguments, for that object's, actual interface, which may differ, in name, in order, or in format, in ways, that the adapter, must handle, transparently, for clients, who should not, need to know, actual interface, at all, to use it, through the adapter, effectively, for their purpose, in the program, as a whole, over its run, with real data, and real requirements, for simplicity, and for compatibility, generally. That wrapping, is the adapter's, essential structure, regardless, of whether, it is implemented, as a class, that holds, a wrapped instance, or, in Python, more simply, as a function, that performs, the translation, for a particular, use case, without a full, adapter class hierarchy, which, as noted, for other patterns, should be, simplified, where Python's features, allow, a simpler expression, without losing, its essential benefit, of decoupling, clients, that they cannot, change, directly, themselves, for reasons, outside their control, such as, library ownership, or legacy risk, generally, and specifically, in a program, over its lifetime, as it evolves, through maintenance, and refactoring, generally.
Adapters, should be used, when, you must, reuse, an existing class, and when, modifying, that class, or modifying, all clients, to match, its interface, is impractical, or risky, compared with, introducing, a single adapter, that centralises, the translation, in one place, maintained, and tested, independently, of both, the adapted class, and its clients, maintenance benefit, over scattering, translation logic, throughout client code, duplicated, in many places, would require, updating, every client, individually, rather than, only the adapter, centrally, in one place, as a whole, over time, generally. Do not, use an adapter, when, you control, both the class, and its clients, and can, simply, change one, directly, without an intermediate, because, adding an adapter, in that case, merely, adds indirection, without benefit, forcing readers, to follow, through the adapter, to understand, what actually happens, when a client, calls a method, through it, for a given, adapted object, in practice, generally, and specifically, for each case, where direct change, would have sufficed, was added, unnecessarily, out of habit, rather than, out of need, for compatibility, between code, that cannot, be changed, directly, themselves, for reasons, generally, and specifically, for that program, at that time, during its development, or its maintenance, as a whole, over time.
Check: When is an adapter justified instead of simply changing a class to match its clients?
Section 10
The decorator pattern, attaches, additional responsibilities, dynamically, without creating, a proliferation, of subclasses, of responsibilities, in practice, of features, of subclasses, that grows, unmanageably, are added, over time, and during, its maintenance, by developers, who must, understandable, and maintainable, in size, over time, generally, and specifically, added, to objects, through decorator, rather than, through subclassing, of features, in practice, generally. A decorator, wraps, an object, providing, so that, clients, can use, exactly, without knowing, decorated, that happens, transparently, behaviour, chooses, to extend, other methods, unchanged, itself, without modification, as seen, by clients, who interact, exactly, for their purpose, in the program, as a whole, over its run, with real data, and real requirements, for flexibility, in adding, responsibilities, dynamically, at run time, rather than, statically, through subclassing, at design time, for every combination, that might ever, be needed, in advance, of knowing, which combinations, in practice, for real objects, with real features, over time, generally.
Decorators, can be stacked, meaning, that an object, can be wrapped, in several decorators, each adding, a different responsibility, such as, logging, or validation, or caching, in any combination, that the program, requires, during execution, with real data, and real requirements, for those responsibilities, specifically, for that object, and that use, in the program, as a whole, over its run, generally. That stacking, main advantage, over subclassing, because, with subclassing, each combination, of responsibilities, would need, its own subclass, leading, of classes, as responsibilities, are added, over time, while, with decorators, each responsibility, that can be, combined, freely, with others, in any order, and any number, without creating, a new class, for each combination, which keeps, the code, manageable, and understandable, even as, responsibilities, grow, in number, and in variety, as new features, are added, and old features, are modified, or removed, for the program, as a whole, over time, generally, and specifically, for each object, decorated, with a combination, of responsibilities, that fits, its particular use, in the program, at that moment, during execution, with real data, and real requirements, for those responsibilities, specifically, for that object, and that use, generally.
In Python, decorator, as a pattern, for objects, should not, be confused, with decorator, for functions, where, a function, is wrapped, by another function, using, a special syntax, above its definition, which is, a related, but distinct, use, of wrapping, to add behaviour, transparently, around an original, whether that original, is a function, or an object, in a program, as a whole, over its run, generally. Both, share, the idea, of wrapping, without modifying, the original, and both, should be used, when, wrapping, adds behaviour, cleanly, without changing, as seen, by its clients, who should, be able, to use, the wrapped version, exactly, as they used, the original, without learning, a new interface, or changing, their code, to accommodate, the wrapping, itself, in any way, that would, couple them, to the decorator, specifically, rather than, generally, of transparent wrapping, as a design, for flexibility, and for maintainability, over time, generally, and specifically, or function, in a program, where decorator, as a pattern, is used, to add behaviour, without modifying, the original, code, or its interface, as seen, by clients, who use it, for their purpose, in the program, as a whole, over its run, generally.
Check: How does stacking decorators avoid the subclass explosion that subclassing would cause?
Section 11
The facade pattern provides a simple interface to a complex subsystem, so that most users can use the subsystem without understanding all of its parts. A subsystem may have many classes, with many interactions between them, and a new user who needs only a small part of its functionality can easily be overwhelmed by having to learn the whole subsystem before doing anything useful. A facade sits in front of the subsystem, offering a few well-chosen operations that cover the common cases, and internally coordinating the subsystem's parts to carry them out. Users who need only those common cases interact solely with the facade, while users with advanced needs can still bypass it and use the subsystem directly, which preserves flexibility for experts without burdening beginners. That layered access, simple by default and detailed when needed, is the facade's main benefit, and it is a pattern you have almost certainly used, even if you never called it by name
A facade should be designed around the use cases its users actually have, not around the subsystem's internal structure. If a facade merely mirrors every subsystem class with a thin wrapper, it adds indirection without simplifying anything, and users must still understand the subsystem to know which facade operations to call, in what order, and with what arguments. A good facade, by contrast, is organised by task: starting a process, saving a result, or generating a report, each expressed as one operation that handles all the internal steps, including steps that users would otherwise forget, or perform in the wrong order. Designing by task requires understanding what users want to achieve, which is harder than mechanically wrapping a subsystem, but it is what makes a facade genuinely simpler to use, rather than merely a different spelling of the same complexity, with the same learning burden, and the same opportunities for error, that the subsystem itself presented, before the facade was introduced, for its users, in practice.
Facades also help when a subsystem changes, because code that uses only the facade is insulated from internal changes, as long as the facade's own interface stays stable. If a subsystem class is replaced, or its internal interaction changes, only the facade needs updating, not every user of the subsystem, which is a significant maintenance benefit for a widely used subsystem, with many users, across many parts of a program. That insulation only holds, however, if users genuinely use the facade, rather than bypassing it to reach the subsystem directly, for convenience, in scattered places, which gradually reintroduces the coupling the facade was meant to remove. A team that introduces a facade should therefore treat direct subsystem use as an exception, to be justified, rather than as a routine alternative, and should review bypasses, to see whether they indicate a missing facade operation, that should be added, so that future users need not bypass it, for the same reason, again, in their own code, over time, as the subsystem, and its users, evolve together, through maintenance, and through new requirements, that the facade, should grow, to cover, deliberately, rather than, by accident, through scattered bypasses, that erode its value, gradually, and silently.
Check: Why should a facade be designed around user tasks rather than mirroring subsystem structure?
Section 12
The proxy pattern provides a stand-in for another object, at all, in most cases, for most purposes, in the program, as a whole, over its run, with real data, and real requirements, specifically, for that object, and that use, generally. Common reasons for a proxy include lazy loading, saving time, and resources, for objects, of the program, with given inputs, that determine, whether the object, is ever needed, at all, in practice, for that run, specifically, rather than, in principle, for all runs, of the program, generally, over time.
Another reason is access control, in the proxy, rather than scattered, that uses it, individually, across the program, where a missed check, would allow, unauthorised use, through a caller, that forgot, to check, or that checked, incorrectly, for a particular operation, in a particular context, during execution, with real users, and real permissions, that matter, for security, and for correctness, alike, over time, generally. A third reason is caching, or remote access, or in a database, or the caching, transparently, without writing, network code, or cache logic, in every place, that uses it, which would duplicate, that logic, in many places, and make it, harder to change, when the remote protocol, or the caching policy, changes, over time, during the program's evolution, and during, its maintenance, by developers, who must, update every duplicated, piece of logic, individually, rather than, only the proxy, centrally, in one place, for the whole program, as a whole, over its run, generally, and specifically, for each proxied object, in a program, over its lifetime, as it evolves, through maintenance, and refactoring, generally.
Proxies should be transparent, in the sense, that they present, the same interface, as the real object, so that callers, need not change, their code, to use a proxy, and so that, a proxy, can be introduced, or removed, without affecting, callers, that the proxy, provides, such as, lazy loading, or access control, or caching, which should be, the only observable difference, between using, the proxy, and using, the real object, directly, for a given caller, in a given context, during execution, with real data, and real requirements, for that behaviour, specifically, for that object, and that use, generally. If a proxy, changes the interface, even slightly, it is no longer, a transparent proxy, but a different kind, of wrapper, such as, an adapter, discussed earlier, which changes, an interface, deliberately, to match, a different expectation, for clients, who need, a different interface, than the wrapped object, provides, naturally, on its own, without adaptation, for their purpose, in the program, as a whole, over its run, generally. Keeping, that distinction, clear, helps you, choose, the right pattern, whether, you need, transparent added behaviour, through a proxy, or interface translation, through an adapter, or additional responsibilities, through a decorator, each of which, wraps, an object, but for, a different purpose, with different consequences, for callers, and for maintainability, over time, generally, and specifically, for each wrapping, in a program, where the pattern, chosen, should match, the problem, actually being solved, rather than, being chosen, by habit, or by confusion, between similar patterns, that wrap objects, in superficially similar ways, but with different intents, and different effects, on the code, that uses them, in practice, generally.
Check: What makes a proxy transparent, and why does that transparency matter?
Section 13
The composite pattern lets clients treat individual objects and groups of objects uniformly, without change, possibly nested, organisation charts, file systems, recursively, actually requires, in practice, that varies, itself, rather than, predetermined depth, enumerated, in advance, without recursion, itself, impossible, for data, whose depth, is not known, until run time, and that varies, with each instance, of the structure, in practice, generally, and specifically, in a program, where composite, provides, a uniform way, to handle, leaves and groups, alike, that both implement, consistently, for all operations, that apply, to both, such as, rendering, or counting, or searching, through the structure, as a whole, however deep, it may be, with given data, during execution, with real workloads, and real requirements, for uniformity, in handling, leaves and groups, alike, generally.
The key idea is a common interface, shared by leaves, and composites, which contain children, that may themselves, be leaves, or composites, so that, an operation, defined, on that interface, such as, rendering, or calculating, a total, can be implemented, by a leaf, directly, for itself alone, by delegating, to its children, and combining, their results, for a group, as a whole, rather than, alone, that mirrors, the recursive structure, of the data, itself, naturally, and elegantly, through code, that handles, a group, by handling, each child, in turn, without needing, to know, whether that child, is a leaf, or another group, because both, and both, respond, in a way, appropriate, leaf or group, respectively, for that operation, specifically, generally, however deep, it may be, with given data, during execution, with real workloads, and real requirements, for recursive handling, of tree-like data, in practice, generally.
Composite is most valuable when client code would otherwise need to distinguish, constantly, with conditional logic, at every point, where an item, or a group, might appear, which is tedious, error-prone, as new kinds, of items, or groups, are added, over time, and during, its maintenance, by developers, who must, update every conditional, individually, to handle, the new kind, correctly, in every place, are distinguished, throughout the program, as a whole, over time, generally. By giving, both, the same interface, composite, eliminates, because client code, can simply, call the operation, leaf or group, without checking, which it is, first, and without, handling each case, separately, in every place, where the operation, is needed, for an item, or a group, in the program, as a whole, over its run, generally. That simplification, is composite's, main benefit, but it, also has, a cost, in that, operations, that make sense, only for groups, such as, adding a child, must either, be part, where they make, little sense, for leaves, to add, or be kept, separate, where client code, that needs, to build, a structure, must then, distinguish, groups from leaves, after all, for building purposes, even though, for operations, that apply, to both, such as, rendering, or counting, in interface design, that should be, decided, deliberately, based on, whether building, or using, a structure, and more important, activity, for clients, of that structure, with given requirements, for its use, over time, generally, and specifically, in a program, where that trade-off, must be weighed, carefully, before committing, to a particular, interface design, alike, as it evolves, through maintenance, and refactoring, generally.
Check: How does the composite pattern let client code treat single items and groups uniformly?
Section 14
The flyweight pattern reduces memory use by sharing objects that are used in large numbers, that repeat, frequently, in practice, worthwhile, compared with, the cost, of managing, shared objects, that provides, shared instances, on demand, of object, needed, generally, and specifically, where flyweight, is applied, to reduce, memory use, significantly, each consume, memory, redundantly, that could, instead, share it, shared object, per kind, over time, generally.
The key distinction in flyweight is between intrinsic state, which is shared, and extrinsic state, which varies, with context, by the client, each time, the shared object, is used, such as, a character's position, in a document, on a map, which the client, knows, as a parameter, when invoking, an operation, on it, for that context, specifically, rather than, itself, where it would, prevent sharing, because each use, would then, with its own, varying state, stored inside it, defeating, the purpose, of sharing, entirely, for that kind, of object, in that program, as a whole, over its run, generally. Designing, a flyweight, therefore, involves, separating, carefully, for each kind, of object, to be shared, so that, as much state, as possible, is intrinsic, and shared, while, only the genuinely, varying state, is extrinsic, and supplied, per use, by clients, who know, their own context, for each use, specifically, and who can, therefore, provide, the extrinsic state, accurately, for that use, needing, to store it, itself, across uses, that would, otherwise, conflict, with each other, through shared, varying state, that cannot, be shared, safely, between different contexts, that require, different values, for that state, during execution, with real data, and real contexts, that vary, generally, and specifically, for each flyweight, in a program, over its lifetime, in many contexts, with many different, extrinsic values, over time, generally.
Flyweight is a specialised optimisation, when measurement, shows, that memory use, is actually, a problem, for a program, as discussed, for performance work, generally, where optimisation, should follow, measurement, rather than, speculation, about a problem, in practice, for real data, and real workloads, that the program, actually handles, in production, or in testing, with realistic data, and realistic volumes, that reveal, whether memory, is truly, a constraint, for that program, specifically, specifically, over its run, generally. For most programs, the number, of similar objects, is modest, and the memory, they consume, is not, a bottleneck, compared with, other uses, of memory, in the program, as a whole, over its run, generally, so adding, flyweight machinery, with its factory, merely, adds complexity, without benefit, making code, harder to read, for no measurable, memory saving, that users, would notice, or that would, prevent a failure, in practice, for real runs, of the program, with real data, and real volumes, generally, and specifically, for each program, where flyweight, is considered, but not, measured, as necessary, before adoption, as a design, for that program, as a whole, over its lifetime, generally. When, memory measurement, does show, a genuine problem, flyweight, can be, a dramatic saving, reducing, memory, from one object, per use, to one object, per kind, plus, small extrinsic state, per use, supplied, by clients, worth its complexity, specifically, specifically, over its run, with real data, and real volumes, that justify, the optimisation, through evidence, rather than, through speculation, alone, generally.
Check: What is the difference between intrinsic and extrinsic state in a flyweight?
Section 15
Behavioural patterns deal with how objects interact, between them, so that, emerges, from collaboration, between objects, rather than, monolithic object, that tries, itself, over time, are added, and during, its maintenance, by developers, who must, understandable, in size, over time, generally, and specifically, that arises, during development, or maintenance, as a whole, over its lifetime, generally. Where creational patterns, focus, on how objects, are created, and structural patterns, focus, on how objects, are composed, behavioural patterns, focus, on how objects, communicate, and cooperate, to achieve, a program's purpose, through messages, events, requests, and responses, that flow, between them, in patterns, that can be, named, described, and reused, across many programs, with similar, interaction problems, and similar, collaboration needs, for their objects, in practice, generally, and specifically, covered, despite their differences, in detail, interaction problems, they solve, for different situations, with different constraints, on coupling, flexibility, and complexity, when choosing, among them, with particular requirements, and particular users, over time, generally.
Common behavioural problems, include, notifying, many objects, when something, changes, without coupling, the changed object, tightly, individually, which would make, a matter, of changing, the changed object, itself, over time, as observers, are added, or removed, and during, its maintenance, by developers, who must, and its observers, in sync, across many changes, to either side, of the relationship, between them, generally. Another, is choosing, an algorithm, at run time, from among, several alternatives, without hard-coding, the choice, into the code, in a way, that makes, a matter, of changing, that code, itself, over time, as alternatives, are added, or removed, and during, its maintenance, generally. A third, is encapsulating, a request, as an object, so that, it can be, stored, queued, logged, or undone, as a first-class, thing, in the program, rather than, as a mere, method call, that happens, immediately, and cannot, be stored, or reversed, once made, which limits, what a program, can do, with requests, in practice, for real users, who may want, to undo, a request, for later execution, when resources, become available, for its processing, generally, and specifically, for each request, in a program, where encapsulation, as an object, enables, those capabilities, cannot provide, on its own, without additional machinery, to store, or reverse it, in practice, generally.
Behavioural patterns, solve, these problems, by defining, clear roles, for the objects, involved, in an interaction, and clear protocols, for how they, communicate, so that, each object, knows, to know, about its collaborators, and so that, new collaborators, or new behaviours, can be added, without changing, existing objects, more than necessary, to work, correctly, in practice, for real programs, with real interactions, between real objects, over time, generally. As you learn, each behavioural pattern, ask, first, what interaction problem, it solves, second, whether your program, actually has, that problem, in a form, that justifies, the pattern's complexity, and third, whether a simpler, Pythonic approach, such as, using, a function, or a simple, event list, would serve, just as well, which, as noted, in the introduction, should be preferred, whenever it suffices, in practice, generally, and specifically, that arises, during development, or maintenance, of the program, as a whole, over time, generally, and specifically, considered, for that problem, in turn, over the course, of this chapter, as a whole.
Check: What distinguishes behavioural patterns from creational and structural patterns?
Section 16
The observer pattern defines a one-to-many relationship, called observers, in response, in detail, when notified, through which, informed, for all observers, alike, regardless, to the change, in question, generally, and specifically, for each observer, registered, with the subject, during execution, and real observers, in the program, as a whole, over its run, generally. That decoupling, main benefit, because, it allows, new observers, to be added, without changing, the subject, itself, simply, by registering, a new observer, that implements, the notification interface, receive notifications, changes, in future, without any change, to accommodate, that new observer, specifically, by name, or by type, in its implementation, which would, otherwise, couple it, tightly, to every observer, individually, and make, adding observers, a matter, of changing, the subject, over time, as observers, are added, or removed, and during, its maintenance, by developers, who must, keep the subject, and its observers, in sync, across many changes, to either side, of the relationship, between them, generally.
Observers are useful for many situations, including user interfaces, knowing, about every display, that shows it, individually, by name, or by type, in its implementation, which would, couple data, to presentation, in a way, that makes, a matter, of changing, the data object, itself, over time, as displays, are added, or removed, and during, its maintenance, generally. They are also useful, for event systems, where many parts, of a program, need to react, to an event, such as, a file changing, knowing, about every reactor, individually, and without, reactors, needing, to poll, the source, repeatedly, to detect, changes, which would waste, time, and resources, as discussed, for polling, generally, where event-driven notification, than repeated checking, for changes, in practice, for real events, with real frequencies, of occurrence, over time, generally, and specifically, for each event, in a program, where observers, provide, efficient, event-driven updates, without polling, by any observer, for changes, from the subject, in question, generally.
Care is needed, with observers, around, notification order, and around, observers, that modify, the subject, during notification, which can cause, confusing, or inconsistent updates, again, while other observers, are still, being notified, in a sequence, that was not, anticipated, for notification, in a single, coherent pass, over its observers, that all observers, can rely on, in practice, generally. A related care, is managing, observer lifetimes, so that, observers, are unregistered, from the subject, rather than, remaining registered, indefinitely, and receiving, notifications, for a subject, which wastes, time, and which can, prevent, an observer, from being, reclaimed, by memory management, because the subject, still holds, a reference, to it, even though, no other part, of the program, still needs it, for any purpose, in practice, generally, and specifically, for each observer, in a program, over its lifetime, notified, and eventually, unregistered, needed, in the program, as a whole, over its run, generally. In Python, observer, can often, be implemented, simply, with a list, of callback functions, that the subject, calls, without a full, observer class hierarchy, which, as noted, for other patterns, should be, simplified, where Python's features, allow, a simpler expression, without losing, its essential benefit, of decoupling, subjects from observers, through a common, notification mechanism, that both sides, understand, and use, consistently, for their interaction, in the program, as a whole, over time, generally.
Check: Why does the observer pattern let new observers be added without changing the subject?
Section 17
The strategy pattern defines a family of algorithms, or function, which algorithm, to use, without hard-coding, the choice, that makes, a matter, of changing, that code, itself, over time, as alternatives, are added, or removed, and during, its maintenance, by developers, who must, in sync, between them, generally. A classic example, is sorting, or formatting, where, several algorithms, or formats, are available, and where, depends, on input, rather than, made, regardless, that determine, which algorithm, specifically, rather than, generally, at once, as a whole, over time, generally, and specifically, for each task, where strategy, provides, run-time choice, among alternatives, for that task, in practice, generally.
In Python, a strategy, is often, simply, a function, passed, as an argument, to the code, that needs, an algorithm, for its task, because, functions, are first-class values, in Python, that can be, passed around, and called, when needed, with particular inputs, that determine, which function, is most appropriate, for that task, at that moment, during execution, with real data, and real requirements, for the algorithm, in question, generally. That function-based, strategy, is far simpler, than a class-based, strategy hierarchy, with a common, strategy interface, and many, strategy classes, each implementing, one algorithm, behind that interface, which, in other languages, without, first-class functions, is necessary, to make, algorithms, interchangeable, as objects, that can be, selected, at run time, and invoked, for their task, generally, and specifically, for each algorithm, in a family, where a class, is needed, to carry, state, or configuration, for that algorithm, beyond what, a simple function, can carry, through its arguments, alone, without additional, state, stored, in an object, that persists, across calls, to that algorithm, with particular configuration, that must be, remembered, between calls, in practice, generally, and specifically, for each strategy, that needs, such persistent state, or configuration, to perform, its algorithm, correctly, for its task, in the program, as a whole, over its run, generally.
Use, strategy, when, a program, genuinely needs, to choose, among several algorithms, at run time, based on, input, or configuration, or user choice, and when, new algorithms, are expected, to be added, over time, without changing, the code, that uses them, for its task, in the program, as a whole, over its run, generally. Do not, use strategy, when, only one algorithm, is ever used, in practice, for a task, and when, adding, strategy machinery, merely, adds indirection, without benefit, forcing readers, to follow, through a strategy, to understand, what algorithm, is actually used, of the program, with given inputs, and given configuration, that determine, the choice, for that run, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally. A single, well-chosen algorithm, implemented, directly, in the code, that needs it, is clearer, and simpler, than a strategy, for a task, that never, actually varies, in practice, for real users, with real inputs, and real configuration, that never, actually select, a different algorithm, for that task, in any run, of the program, as a whole, over its lifetime, generally, and specifically, for each task, where strategy, was considered, but where, a single algorithm, suffices, of that task, in the program, in practice, generally, and specifically, over time, change, or remain, stable, for their tasks, in practice, generally.
Check: Why is a strategy in Python often just a function passed as an argument?
Section 18
The command pattern encapsulates a request as an object, queued, logged, or undone, that happens, immediately, or reversed, once made, which limits, can do, with requests, in practice, to undo, a request, when resources, become available, generally, and specifically, where encapsulation, enables, those capabilities, cannot provide, to store, in practice, generally. A command object, typically, to execute, the request, and, optionally, a method, by reversing, its effects, so that, a program, can maintain, a history, of commands, executed, and can, undo, them, requests, an undo, to reverse, over its run, with real actions, that determine, which commands, must be undone, to restore, the program's state, before those commands, were executed, generally, and specifically, for each command, in the history, that may, or may not, be undone, depending on, user actions, over time, generally.
Commands are useful for many situations, including user interfaces, is a command, that can be, undone, and redone, as the user, requests, that operate, rather than, directly, which would be, hard to reverse, without knowing, exactly what, each action, changed, and how, to reverse, that change, precisely, for each kind, of action, in the program, as a whole, over its run, generally. They are also useful, for queuing, where requests, are stored, in a queue, and executed, later, when resources, become available, or when, a worker, becomes free, to process, the next request, in the queue, in order, needing, to wait, for execution, to complete, before continuing, in the program, as a whole, over its run, generally. They are also useful, for logging, and for auditing, where each command, can be, recorded, before execution, so that, a program, can reconstruct, what happened, from its log, of commands, executed, in order, over time, which is valuable, for debugging, and for accountability, in programs, where actions, matter, for users, and for systems, for correct behaviour, and for trust, over its lifetime, generally, and specifically, for each command, logged, in the program, as a whole, over its run, generally.
Implementing, command, in Python, can be, as simple, as storing, a function, together with, its arguments, or tuple, that can be, executed, later, by calling, the stored function, Pythonic expression, without a full, command class hierarchy, with a common, command interface, and many, command classes, each implementing, one kind, of request, behind that interface, which, in other languages, without, first-class functions, is necessary, to make, requests, storable, as objects, that can be, executed, later, for all commands, alike, regardless, in practice, generally. A class-based, command, is still, appropriate, when, a command, needs, to carry, significant state, for undo, such as, the previous values, of data, it changed, during execution, which must be, remembered, to reverse, the change, accurately, during undo, for that command, specifically, rather than, merely re-executing, an inverse operation, that may not, restore, the original state, exactly, for all cases, with all possible, prior states, that it might, have changed, from, during its execution, in practice, generally, and specifically, for each command, that supports, undo, through stored, prior state, rather than, alone, which may be, insufficient, for exact restoration, of prior state, in all cases, generally, and specifically, over its lifetime, in a program, where undo, matters, for users, and for correctness, after undo, operations, are performed, in practice, generally.
Check: How does encapsulating a request as an object enable undo and queuing?
Section 19
The iterator pattern provides a standard way to traverse a collection, element by element, over many kinds, of collections, without knowing, whether the collection, is a list, or a tree, or a stream, of data, arriving, over time, from a file, or a network, in a sequence, that must be, processed, element by element, as it arrives, rather than, all at once, in memory, which would be, wasteful, or impossible, for large, or unbounded, sequences, of data, that cannot, fit in memory, at once, with given data, that constrain, how much, can be held, at once, during execution, with real workloads, that determine, whether in-memory, processing, is feasible, at all, during its run, generally, and specifically, for each collection, where iterator, provides, sequential access, without in-memory, loading, at once, in practice, generally.
In Python, iterator, is built into, the language, itself, through a protocol, that any object, can implement, to be, iterable, meaning, that it can, be used, in a loop, that traverses, its elements, one by one, without the loop, needing, to know, or how, its elements, are stored, or generated, during traversal, simplification, over languages, where iterator, must be, implemented, explicitly, as a separate, iterator class, for each kind, of collection, with methods, to advance, and to test, for completion, that clients, must call, directly, which is, more verbose, and more error-prone, than Python's, built-in loop, that handles, iteration, automatically, through the protocol, for any iterable, object, regardless, of its kind, in practice, generally, and specifically, for each iterable, in a Python program, where the language, itself, provides, the iterator pattern, as a core feature, rather than, as a pattern, to be implemented, from scratch, for each collection, in each program, that needs, sequential access, to its elements, over time, generally.
Because, Python, provides, iterator, natively, the pattern, in its textbook form, is rarely, needed, as a separate design, in Python programs, except, when, you need, to define, a custom traversal, for a custom collection, such as, traversing, a tree, in a particular order, or generating, elements, lazily, from a computation, rather than, from stored data, in a way, that the built-in, loop, can use, transparently, through the protocol, without clients, needing, to know, about the custom traversal, itself, in detail, for their loop, to work, correctly, for that collection, specifically, and for that traversal, specifically, over its elements, in order, as defined, by the custom iterator, for that collection, in that program, as a whole, over its run, generally. In those cases, implementing, the iterator protocol, for your collection, is the Pythonic way, to provide, the iterator pattern's benefits, of uniform traversal, without exposing, internal structure, to clients, who should, simply loop, over your collection, as they would, over any other, iterable, in Python, without special code, for your collection's, particular structure, or traversal order, which is the essence, of the pattern, expressed, through the language's, own protocol, rather than, through a separate, iterator class hierarchy, that would, duplicate, what the language, already provides, for all iterables, alike, in Python, generally, and specifically, for each custom collection, where a custom traversal, is genuinely needed, beyond what, built-in collections, already provide, through their own, iterators, for their elements, in order, as stored, or as generated, during traversal, in practice, generally.
Check: Why is the textbook iterator pattern rarely needed as a separate design in Python?
Section 20
The template method pattern defines the skeleton of an algorithm, that calls, steps, while, letting, subclasses, override, individual steps, to provide, specific behaviour, without changing, or order, of steps, defines, alike, when, several variants, share, but differ, steps, such as, reading data, processing it, and writing, a result, where, the overall flow, is the same, for all variants, but, the reading, or processing, or writing, step, differs, by variant, depending on, the data source, or the processing, required, for that variant, specifically, rather than, generally, for all variants, at once, as a whole, over time, generally, and specifically, for each variant, of the algorithm, in a program, where template method, provides, a shared skeleton, with customisable steps, for each variant, alike, generally.
Template method, works, by inverting, control, compared with, ordinary, method calls, because, the base class, skeleton, calls, subclass steps, rather than, subclass code, calling, base class helpers, in an order, chosen, by the subclass, itself, of inversion, that can be, confusing, at first, for programmers, unfamiliar, with the pattern, who expect, subclasses, to drive, the algorithm, rather than, to be driven, skeleton, that orchestrates, their steps, that they cannot, change, without changing, the skeleton, itself, which is, deliberately, fixed, to ensure, that all variants, follow, for correctness, and for consistency, across variants, in the program, as a whole, over its run, generally. That fixed structure, is template method's, main benefit, when, the structure, itself, matters, for correctness, such as, when steps, must happen, or when, certain steps, must always, be performed, for every variant, without exception, for any variant, however it, customises, its individual steps, in detail, in the program, as a whole, over its run, generally, and specifically, for each variant, where the skeleton, guarantees, that required steps, are never, skipped, by a variant, that forgets, to call them, in its own, custom implementation, of the algorithm, from scratch, without a skeleton, to guide it, in order, as defined, for all variants, alike, generally.
In Python, template method, can often, be expressed, more simply, by passing, functions, to a single, function, that implements, the skeleton, and calls, those functions, in order, for each step, rather than, defining, a base class, and subclasses, for each variant, with overridden, step methods, which is, a class-based expression, that is, more verbose, and less flexible, than the function-based, expression, for many cases, where steps, do not need, persistent state, or complex, subclass hierarchies, to carry, their customisation, from one step, to the next, with given data, and given requirements, for its steps, specifically, rather than, generally, for all variants, at once, as a whole, over time, generally. Use, template method, when, variants, genuinely need, subclass state, or when, a hierarchy, already exists, for other reasons, that template method, can build on, naturally, without adding, a separate hierarchy, solely, for the template, itself, which would be, unnecessary complexity, for a program, where a function-based, skeleton, would serve, just as well, with far less, machinery, and far less, maintenance burden, as it evolves, change, in ways, that a simpler, function-based approach, can accommodate, without the full, class hierarchy, of template method, generally, and specifically, for each algorithm, where template method, is considered, as a design, for its variants, over time, as a whole.
Check: How does template method use inversion of control to guarantee a fixed algorithm structure?
Section 21
The state pattern lets an object change its behaviour when its internal state changes, for each state, to test, its current state, before deciding, what to do, for each operation, in each state, which quickly, becomes, hard to read, hard to test, as new states, are added, over time, and during, its maintenance, by developers, who must, update every conditional, individually, to handle, the new state, correctly, in every method, where state, affects behaviour, throughout the object, as a whole, over its lifetime, generally, and specifically, for each method, where state-dependent behaviour, appears, as a whole, over time, generally. A classic example, is a document, that can be, in draft, review, or published state, where, operations, such as, editing, or publishing, behave, differently, depending on, and where, such as, archived, should not, require, changing, every method, that checks, state, individually, as a whole, over its lifetime, generally, and specifically, for each method, affected, in practice, generally.
With the state pattern, each state, is represented, by an object, that implements, the operations, in the way, appropriate, for that state, alone, so that, editing, in draft state, succeeds, while editing, in published state, is rejected, or handled, differently, own implementation, for that state, specifically, rather than, through a conditional, in a single, editing method, that tests, state, and branches, accordingly, for all states, at once, in one place, that grows, added, over time, and during, its maintenance, generally. The main object, holds, a reference, and forwards, operations, to it, so that, changing state, is simply, a matter, of replacing, the current state object, with another, for the new state, without changing, the main object's code, at all, for the transition, itself, except, for the assignment, to its state reference, in a single place, where transitions, are managed, centrally, for the object, as a whole, over its lifetime, generally, and specifically, for each transition, between states, that the object, can undergo, during its use, in the program, as a whole, over its run, generally.
State is most valuable when an object has several states, with behaviour, that differs, significantly, between them, and when, new states, are expected, to be added, over time, without changing, existing state behaviour, more than necessary, for the new state, to coexist, with old states, consistently, for the object, as a whole, over its lifetime, generally. For an object, with only, two states, and simple, differences, between them, a conditional, in a few methods, may be, clearer, and simpler, than a full, state hierarchy, with a class, for each state, and a common, state interface, that all states, must implement, for every operation, even operations, that make little sense, for a particular state, which must then, be implemented, as a rejection, or as a no-op, for that state, specifically, adding, boilerplate, without benefit, for a simple, two-state object, whose behaviour, is easily understood, from a few conditionals, in its methods, directly, without indirection, through state objects, that must be, read, and understood, in addition, to the main object, itself, to know, what happens, in each state, for each operation, in practice, generally, and specifically, for each simple object, where state, was considered, but where, conditionals, suffice, for its actual, state-dependent behaviour, over its lifetime, as a whole, over time, generally.
Check: How does the state pattern avoid scattering state conditionals through every method?
Section 22
The chain of responsibility pattern passes a request along a chain of handlers, which handler, will ultimately, handle it, or even, at all, registered, during execution, that determine, the outcome, specifically, rather than, generally, at once, over time, generally. That decoupling, because, it allows, handlers, removed, or reordered, without changing, the sender, itself, simply, by changing, where handlers, are linked, together, in order, for processing, requests, generally, and specifically, for each chain, of handlers, in a program, where chain composition, is managed, centrally, rather than, scattered, through sender code, that would otherwise, need to know, every handler, individually, and to choose, among them, explicitly, for each request, it sends, in practice, generally.
A common example, is request validation, or approval, where, a request, must pass, through several checks, in order, such as, format checking, permission checking, and limit checking, each performed, in the chain, that either, rejects the request, if it fails, its check, or passes it, in sequence, until, either a handler, rejects it, or all handlers, accept it, and the request, is considered, approved, for processing, by the program, as a whole, over its run, generally. Another example, is event handling, in user interfaces, where, an event, such as, a key press, is passed, along a chain, of interface elements, until, one element, handles it, or none does, and the event, is ignored, as unhandled, for that run, of the program, with that event, specifically, rather than, generally, for all events, at once, as a whole, over time, generally, and specifically, for each event, in a program, where chain handling, provides, flexible, decentralised dispatch, without a central, dispatcher, that must know, every handler, individually, and choose, among them, explicitly, for each event, it receives, in practice, generally.
Chain of responsibility, should be used, when, may handle, a request, and when, the handler, is not known, in advance, at the sender, but depends, on run-time conditions, that can change, without changing, the sender, itself, added, over time, and during, its maintenance, generally. It should not, be used, when, the handler, is always, the same, and when, calling it, directly, is clear, and simple, because, adding a chain, in that case, merely, adds indirection, without benefit, forcing readers, to follow, through the chain, to understand, what actually handles, a given request, of the program, with given handlers, registered, in the chain, at that time, during execution, with real requests, and real handlers, that determine, the outcome, for each request, specifically, rather than, generally, for all requests, at once, as a whole, over time, generally. A related care, is ensuring, that a request, that no handler, handles, is dealt with, sensibly, rather than, silently dropped, without any indication, to the sender, that it was, unhandled, which can cause, confusion, for users, who expect, a response, to their request, and who receive, none, because no handler, claimed it, in the chain, as composed, for that run, of the program, with those handlers, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally, and specifically, in a program, where chain handling, must define, a default, for unhandled cases, explicitly, rather than, leaving them, to silent failure, that users, cannot diagnose, or correct, for their work, in practice, generally.
Check: Why does chain of responsibility decouple a request's sender from its eventual handler?
Section 23
The mediator pattern defines an object that coordinates communication between other objects, individually, which reduces, coupling, between them, from many-to-many, knows, object, directly, to many-to-one, knows, only about, the mediator, through which, it communicates, with others, indirectly, generally, and specifically, of interacting objects, in a program, where mediator, centralises, their interaction, in one place, that can be, understood, and changed, without changing, every object, individually, added, over time, and during, its maintenance, by developers, who must, keep interacting objects, in sync, across many changes, to their interaction, patterns, and protocols, generally, and specifically, for each interaction, mediated, rather than, through direct links, between every pair, of interacting objects, in a group, as a whole, over time, generally.
A classic example, with many controls, that interact, such as, a form, where, changing one field, affects, what other fields, show, or whether, a button, is enabled, for submission, depending on, and the values, entered, so far, by the user, during their work, with the form, of the program, with given inputs, and given interactions, between controls, that determine, the form's behaviour, as a whole, over that session, generally. Without a mediator, each control, would need, to know, it affects, directly, leading, of references, between controls, that is, hard to understand, hard to test, is added, or when, an interaction rule, changes, for existing controls, in the form, as a whole, over its lifetime, generally. With a mediator, each control, notifies, the mediator, when it changes, and the mediator, decides, how other controls, should respond, centrally, in one place, where interaction rules, live, together, and can be, read, understood, and changed, without touching, every control, individually, over time, and during, its maintenance, by developers, who must, keep interaction rules, consistent, across all controls, in the form, as a whole, over its lifetime, generally, and specifically, for each rule, that governs, interaction, between controls, in that form, in practice, generally.
Mediator is most valuable when a group of objects interacts, in complex, interdependent ways, that would otherwise, require, many direct links, between them, and when, those interaction rules, are expected, to change, over time, independently, of the objects, themselves, whose individual behaviour, remains, relatively stable, while, their interaction, evolves, with new requirements, for how they, cooperate, in the program, as a whole, over its run, generally. For a small group, with simple, stable interactions, a mediator, adds, an extra object, of indirection, without benefit, forcing readers, to follow, through the mediator, to understand, what happens, when one object, changes, and how, others respond, to that change, of the program, with given objects, that determine, the outcome, for that change, specifically, rather than, generally, for all changes, at once, as a whole, over time, generally. A related risk, is that, the mediator, itself, becomes, overly complex, as it absorbs, all interaction logic, growing, into a monolith, that is, itself, hard to understand, over time, as interaction rules, accumulate, without structure, to organise them, clearly, for readers, and for maintainers, who must, over its lifetime, generally, and specifically, for each mediator, in a program, where its complexity, must be, managed, through clear organisation, of its rules, and through, splitting, it, if it grows, too large, to handle, coherently, for the group, it mediates, in practice, generally.
Check: How does a mediator change many-to-many coupling between objects into many-to-one?
Section 24
The memento pattern captures and stores an object's internal state, without violating, its encapsulation, meaning, without exposing, them, to restore, the object, correctly, generally, and specifically, whose state, must be, saved, and restored, during execution, that determine, when, and how, a memento, is created, and used, specifically, rather than, generally, at once, over time, generally. A classic use, is undo, where, changes, a memento, is created, capturing, its state, so that, the object, can be, restored, exactly, without needing, to know, how, to reverse, the operation, itself, restore, exactly, with all possible, prior states, that it might, have changed, from, during its execution, in practice, generally, and specifically, for each operation, that supports, undo, through memento, rather than, alone, which may be, insufficient, for exact restoration, of prior state, in all cases, generally.
A memento, typically, from the perspective, of the code, that stores it, meaning, that storing code, can hold, a memento, and pass it, back, for restoration, later, without inspecting, or modifying, its contents, directly, because, its contents, internal state, which only, that object, itself, should understand, in detail, for its own, restoration, purposes, specifically, rather than, generally, for all objects, at once, as a whole, over time, generally. That opacity, preserves, encapsulation, because, outside code, does not need, to know, to save, and restore, its state, benefit, over approaches, that require, outside code, to read, and write, an object's internals, directly, for saving, and restoration, which would, couple outside code, tightly, to those internals, and break, whenever, the internals, change, during refactoring, or evolution, of the object, over its lifetime, generally, and specifically, for each object, whose internals, are encapsulated, behind its interface, and should, remain so, even when, its state, must be, saved, and restored, externally, for undo, in the program, as a whole, over its run, generally.
Memento should be used, when, state, must be, saved, and restored, exactly, and when, encapsulation, matters, for the object, whose state, is being saved, because, its internals, should not, be exposed, to saving code, that merely, holds a memento, for later restoration, without needing, to understand, its contents, in detail, in the program, as a whole, over its run, generally. It should not, be used, when, state, is simple, and public, and when, saving it, is merely, a matter, of copying, that outside code, can already, read, and write, directly, without violating, any encapsulation, because, in that case, a memento, merely, adds indirection, without benefit, forcing readers, to follow, to understand, what state, is actually saved, and restored, of the program, with given state, that determine, what must be, saved, for that object, specifically, rather than, generally, for all objects, at once, as a whole, over time, generally. A related care, is memory use, because, storing, many mementos, of states, can consume, significant memory, especially, for objects, with large state, that changes, frequently, with real data, and real workloads, that determine, how many mementos, are stored, and how large, each one is, over its lifetime, generally, and specifically, for each history, of mementos, in a program, where memory, must be, managed, through limits, on history length, or through, incremental saving, of only, changed parts, of state, rather than, full snapshots, for every change, to an object, over its run, generally.
Check: How does a memento preserve encapsulation while allowing state to be restored?
Section 25
The visitor pattern lets you define a new operation, of objects, without changing, the classes, themselves, by separating, the operation, that visits, each object, and performs, the operation, specific kind, through methods, specialised, generally, and specifically, visited, in order, as defined, specifically, rather than, generally, at once, over time, generally. That separation, is useful, when, a structure, but, operations, on it, change, frequently, frequently, over time, and during, its maintenance, by developers, who must, without changing, individually, tedious, and error-prone, of objects, generally, and specifically, added, over time, are needed, in practice, generally.
A classic example, with kinds, such as, paragraphs, images, and tables, where, operations, such as, rendering, exporting, and counting, words, must be, performed, differently, and where, new operations, such as, should be, addable, without changing, the paragraph, image, themselves, over time, as new formats, are required, by users, for their work, with documents, of those kinds, in the program, as a whole, over its run, generally. With visitor, each operation, is a visitor, with a method, for each kind, in the structure, so that, is a matter, of writing, a new visitor, with methods, for all kinds, without touching, the object classes, themselves, at all, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally. That is, the reverse, of the usual, object-oriented trade-off, where, is easy, but adding, a new operation, requires, changing, every class, to implement, the new operation, for its kind, specifically, and where, visitor, inverts, that trade-off, making, new operations, easy, and new kinds, harder, because, a new kind, requires, updating, every visitor, to handle, the new kind, specifically, in its methods, for all kinds, in the structure, as a whole, over its lifetime, generally, and specifically, added, to the structure, over time, as requirements evolve, of objects, appear, that must be, handled, on the structure, in practice, generally.
Visitor, should therefore, be used, only when, the set, of object kinds, in a structure, is relatively stable, and when, operations, on that structure, change, frequently, or are expected, to be added, frequently, over time, as new requirements, arise, for operations, in practice, generally. If, kinds, change, frequently, instead, visitor, becomes, a burden, because, every new kind, forces, updates, to every visitor, in the program, for that structure, as a whole, over its lifetime, generally, which is, the opposite, of the benefit, visitor, provides, when kinds, are stable, and operations, vary, over time, generally, and specifically, for each structure, where visitor, is considered, as a design, for its operations, and its kinds, over time, as a whole. In Python, visitor, can often, be simplified, by using, functions, and dispatch, based on, an object's kind, rather than, a full, visitor class hierarchy, with a method, for each kind, in each visitor, which, as noted, for other patterns, should be, simplified, where Python's features, allow, a simpler expression, without losing, its essential benefit, of separating, operations, from object classes, for structures, where operations, vary, more than kinds, over time, generally, and specifically, for each structure, where visitor, is applied, in Python, idiomatically, rather than, as a literal, textbook implementation, imported, unchanged, from another language, whose constraints, do not apply, to Python programs, written idiomatically, for real users, with real requirements, in practice, generally.
Check: What trade-off does visitor invert compared with ordinary object-oriented design?
Section 26
The interpreter pattern defines a representation, of classes, rule, together, method, that evaluates, an expression, written, in combination, generally, and specifically, being interpreted, to evaluate, for interpretation, correctly, and efficiently, generally. That representation, is useful, for small, domain-specific languages, such as, simple, filter expressions, where, users, need, to express, conditions, or calculations, textual form, can parse, into a tree, of rule objects, and then, interpret, by traversing, that tree, and evaluating, each rule, in turn, method, for that rule, specifically, rather than, generally, for all rules, at once, as a whole, over time, generally, and specifically, for each expression, in the language, as written, by users, for their purpose, in the program, as a whole, over its run, generally.
Interpreter, is most appropriate, for small, simple grammars, where, the number, of rules, is modest, and where, expressions, are relatively short, and infrequent, with real users, and real workloads, that determine, how often, interpretation, happens, and how large, expressions, typically are, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. For larger, or more complex, languages, interpreter, becomes, unwieldy, because, each rule, requires, its own class, with its own, interpretation method, leading, to many classes, that must be, maintained, together, as the grammar, evolves, are added, over time, and during, its maintenance, by developers, who must, in sync, with the grammar, as a whole, over its lifetime, generally. Performance, is also, a concern, for interpreter, because, interpreting, an expression, by traversing, a tree, of rule objects, and calling, an interpretation method, for each node, in that tree, is slower, than, executing, compiled code, directly, without interpretation, overhead, for each node, visited, during evaluation, of the expression, as a whole, over its structure, generally, and specifically, for each expression, evaluated, frequently, in a program, where interpretation, overhead, accumulates, across many evaluations, into a significant, total cost, in time, and in resources, consumed, by the program, as a whole, over its run, generally.
In Python, interpreter, is rarely, needed, in its full, textbook form, because, Python, itself, provides, powerful tools, for parsing, and evaluating, simple expressions, and because, many, domain-specific needs, can be met, more simply, with data structures, such as, dictionaries, or lists, representing, conditions, or calculations, that a program, evaluates, directly, without defining, a full grammar, with a class, for each rule, in that grammar, specifically, rather than, generally, for all grammars, at once, as a whole, over time, generally. When, a genuine, small language, is needed, for users, to express, rules, or filters, or calculations, that data structures, alone, cannot express, clearly, for those users, in their work, with the program, as a whole, over its run, generally, interpreter, provides, a clean, extensible way, to define, that language, rule by rule, with a class, for each rule, and an interpretation, method, for each class, that can be, tested, individually, for its rule, specifically, without needing, to test, the whole language, at once, for every rule, in combination, in the grammar, as a whole, over its lifetime, generally, and specifically, for each rule, in the grammar, or changed, over time, and during, its maintenance, by developers, who must, keep the grammar, in sync, for correct interpretation, of user expressions, in practice, generally.
Check: When is the interpreter pattern appropriate, and when does it become unwieldy?
Section 27
The null object pattern provides an object that does nothing, specially, that determine, is present, or absent, during execution, for handling, absence, gracefully, without errors, scattered, that uses, the object, for its purpose, in the program, as a whole, over its run, generally, and specifically, for each object, that may be, absent, in a program, where null object, provides, a safe default, for that absence, in practice, generally. A classic example, is a logger, where, code, configured, for its run, can simply, that discards, messages, silently, is present, rather than, checking, whether a logger, exists, before logging, a message, through it, for that call, specifically, rather than, generally, at once, as a whole, over time, generally.
Null object, simplifies, client code, considerably, when, absence, is common, and when, the default behaviour, for absence, is simply, to do nothing, or to return, that client code, can use, without distinction, for its purpose, in the program, as a whole, over its run, generally. Without null object, every use, of an object, must be, guarded, by a check, for absence, before use, which clutters, client code, with repetitive checks, that obscure, its main logic, and that are, easy to forget, in one place, among many, where the object, is used, throughout a program, leading, to errors, allows, code, to use, an absent object, present, and to fail, at run time, when it tries, to call, a method, on absence, itself, rather than, that would, have handled, the call, safely, by doing nothing, or by returning, a default, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally, and specifically, for each use, of an object, in a program, where null object, removes, the need, for repetitive, absence checks, at every use, in practice, generally.
Null object, should be used, with care, however, because, silently doing nothing, can hide, errors, where, absence, actually indicates, a problem, that should, be reported, rather than, ignored, silently, that discards, calls, without any indication, to the caller, that the object, was absent, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally. If, absence, means, that something, went wrong, such as, a required service, failing to initialise, missing, with real data, and real requirements, for that service, or configuration, specifically, then, code, should fail, loudly, rather than, proceeding, silently, that masks, the underlying problem, until much later, when its effects, become, apparent, and hard, to diagnose, for developers, who must, trace them back, that left, no trace, in logs, or in errors, at the time, it occurred, during execution, with real data, and real conditions, that determined, the failure, for that run, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally. Use null object, therefore, only, when absence, is a legitimate, expected case, with a safe, default behaviour, such as, no logging, when no logger, is configured, for a run, of the program, with given configuration, that determine, whether logging, is desired, for that run, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally, and specifically, for each case, where absence, is expected, and safe, to ignore, silently, in practice, generally.
Check: When is silently doing nothing the wrong response to an absent object?
Section 28
Dependency injection is a technique, closely related, where, an object, receives, the objects, from outside, rather than, creating them, itself, internally, so that, its dependencies, can be, changed, without changing, the object, itself, such as, testing, where, can be, injected, in place, or configuration, where, can be, injected, depending on, in which, specifically, rather than, generally, at once, over time, generally. Injection, typically, happens, that takes, dependencies, as parameters, generally, or through, setter methods, that allow, dependencies, to be, changed, after construction, that must, be created, are known, or that must, have its dependencies, replaced, during its lifetime, in the program, as a whole, over its run, generally, and specifically, for each object, whose dependencies, are injected, rather than, created internally, by that object, itself, in practice, generally.
The main benefit, of injection, is decoupling, between an object, of its dependencies, because, an object, that creates, its dependencies, itself, is coupled, tightly, and cannot, easily be, tested, in isolation, which may be, slow, or external, or hard, to set up, for a test, that isolates, the object, under test, for the purpose, of testing, its logic, alone, without interference, from dependency behaviour, that is, itself, untested, or external, to the program, under test, generally, and specifically, for each object, tested, in isolation, through injected, fake dependencies, that stand in, for real ones, during testing, as discussed earlier, for testing doubles, generally, where doubles, should conform, as real dependencies, they replace, for testing purposes, alone, during development, of the code, under test, generally. Injection, also makes, configuration, more flexible, because, a program, can assemble, its objects, with different dependencies, for different environments, such as, development, testing, or production, without changing, the objects, themselves, simply, by injecting, different dependencies, at assembly time, in one place, where objects, are created, and wired, together, of the program, with given configuration, and given environment, that determine, which dependencies, are appropriate, for that run, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally.
Injection, should be used, for dependencies, that vary, or that need, to be replaced, for testing, or for configuration, and not, for every value, an object, uses, internally, because, injecting, everything, including, simple values, that never vary, and that are, own logic, rather than, external dependencies, on other objects, or services, merely, adds plumbing, without benefit, forcing, callers, to supply, values, that the object, could simply, create, or compute, itself, internally, without any loss, of flexibility, or testability, for its purpose, in the program, as a whole, over its run, generally. A useful rule, is to inject, collaborators, that is, other objects, or services, that an object, works with, to achieve, its purpose, and to create, internally, simple values, and data structures, that are, own implementation, rather than, external collaborators, whose identity, or behaviour, might need, to change, independently, of the object, itself, for testing, or for configuration, generally, and specifically, for each object, whose collaborators, are injected, rather than, created internally, by that object, itself, in practice, generally. That distinction, between collaborators, and internal values, keeps, injection, focused, on the dependencies, that actually benefit, from flexibility, while, avoiding, excessive plumbing, for values, that never vary, need injection, in the program, as a whole, over its run, generally.
Check: What should be injected into an object, and what should it create internally?
Section 29
Patterns and testing interact, because, patterns, easier, or harder, to test, depending on, and because, tests, can reveal, or whether, it merely, added complexity, without benefit, over time, generally, and specifically, applied, where testing, provides, feedback, in practice, specifically, rather than, generally, at once, over time, generally. A pattern, that decouples, objects, through interfaces, generally, typically, makes testing, easier, because, test doubles, can be, substituted, without changing, the code, under test, itself, alone, during development, under test, generally. A pattern, that introduces, global state, such as, singleton, for singleton, generally, typically, makes testing, harder, because, tests, can interfere, that persists, between tests, unless, reset, which is, tedious, and error-prone, compared with, isolated objects, whose state, is created, fresh, shared state, to reset, between tests, in practice, generally, and specifically, where isolation, matters, for reliable, over time, generally.
When, evaluating, a pattern, therefore, consider, its effect, on testing, as part, and benefit, alongside, its effect, on readability, generally. A pattern, that makes, a program, more flexible, for hypothetical, future changes, but, much harder, to test, may be, because, untestable code, is hard, to change, safely, in future, regardless, in principle, for changes, been tested, in practice, generally. Conversely, a pattern, that makes, testing, straightforward, by decoupling, collaborators, doubles, to be, substituted, easily, during tests, is valuable, even if, it adds, some indirection, because, that indirection, in test reliability, and in confidence, that changes, to production code, can be, verified, quickly, through tests, that run, in isolation, without external dependencies, that would, make them, slow, or flaky, if used, directly, in every test, run, generally, and specifically, for each test, in a suite, where isolation, through decoupling, matters, for reliable, repeatable results, over time, generally, and specifically, for each pattern, evaluated, for its effect, on testing, as part, for a program, as a whole, over its lifetime, generally.
Tests, also, serve, as documentation, for patterns, in a program, because, a test, that uses, a pattern's structure, shows, future readers, how the pattern, is intended, to be used, in that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. When, you introduce, a pattern, into a program, write, tests, that exercise, it, through its intended, interface, rather than, alone, so that, future maintainers, can see, the pattern, in action, for realistic uses, and can, change, its implementation, confidently, as long as, its tests, still pass, for those uses, specifically, rather than, generally, for all uses, at once, as a whole, over time, generally. That test coverage, also, guards, against, pattern decay, where, a pattern, that was, well applied, initially, gradually, becomes, misapplied, through later changes, that bypass, its structure, or that add, special cases, that violate, its intent, for the program, as a whole, over its lifetime, generally, because, tests, that exercise, the pattern, through its interface, will fail, when its structure, is violated, in a way, that affects, its intended uses, for real clients, of the pattern, in the program, as a whole, over its run, generally, and specifically, for each test, that guards, over time, are maintained, or misapplied, through later changes, by developers, who may not, understand, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally.
Check: How can a pattern's effect on testing help judge whether it was worth applying?
Section 30
Choosing a pattern, well, involves, asking, of questions, before committing, because, patterns, are tools, not goals, in themselves, and because, a pattern, applied, is worse, at all, for readability, generally, and specifically, considered, over time, generally. First, what problem, solve, in general, and does, my program, actually have, that problem, that matters, in practice, actually handles, in production, and realistic volumes, that reveal, whether the problem, is real, and significant, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and specifically, for each program, where a pattern, is considered, as a solution, to a problem, that may, or may not, actually exist, in that program, in practice, generally.
Second, what does, this pattern, cost, in complexity, in indirection, for this program, specifically, and is, that cost, justified, by the benefit, it provides, for this program's, real requirements, and real future, as far as, they can be, reasonably foreseen, without speculating, about hypothetical, future needs, that may never, actually arise, in practice, for real users, with real requirements, that determine, will ever, actually be used, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Third, is there, a simpler, Pythonic solution, that solves, the same problem, just as well, for this program, specifically, such as, a function, a built-in structure, or a simple, direct design, without pattern machinery, that would, serve, just as well, with far less, complexity, and far less, maintenance burden, as it evolves, change, in ways, can accommodate, which should be, reserved, for problems, where simpler solutions, demonstrably, fall short, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and specifically, for each problem, was considered, and rejected, for good reason, based on, evidence, about that problem's, actual complexity, and actual requirements, in practice, generally.
Fourth, will, this pattern, make, the code, easier, or harder, for future maintainers, to understand, and to change, safely, generally, and specifically, for each maintainer, who will, has moved on, to other work, and who must, understand, the pattern, as applied, in that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. A pattern, that is, well known, to the team, and that, matches, the problem, clearly, will be, understood, quickly, by maintainers, who recognise it, from its name, and its structure, in the code, as a whole, over its lifetime, generally. A pattern, that is, obscure, or applied, for a problem, that does not, clearly match, its intent, will confuse, maintainers, who must, first understand, the pattern, itself, and then, understand, why it was, applied, to a problem, that does not, in that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Answering, those four questions, honestly, before applying, a pattern, prevents, most cases, of pattern overuse, where, enthusiasm, for patterns, leads, to designs, that are, more complex, and that are, harder, to maintain, than simpler designs, would have been, in practice, generally, and specifically, for each design, where a pattern, was applied, without asking, those questions, first, before committing, to its complexity, for a program, as a whole, over its lifetime, generally.
Check: What four questions should you ask before committing to a pattern's complexity?
Section 31
Many patterns appear in Python's own standard library, not under pattern names, discussed earlier, is the iterator pattern, built into the language, so that any object can be looped over, without clients knowing its internal structure. Context managers, use it, and release it reliably, even if an error occurs during use, in every place where the resource is used. Functions that accept a key function for sorting embody the strategy pattern, in its simplest Pythonic form, rather than as a strategy object, with a class hierarchy behind it. Seeing these familiar features as patterns, in disguise, makes patterns less intimidating, because you have already used several of them, successfully, without needing their names, to use them well, in practice, for real programs, with real requirements, over time.
Recognising library patterns also helps you choose the right tool for a problem, in a tested, documented form, that you can use, directly, rather than implementing it, yourself, from scratch, with all the bugs, and edge cases, that a fresh implementation, inevitably contains, before it has been, tested, thoroughly, against realistic data, and realistic workloads, over time, in production, or in testing, with real users, depending on it, for correct behaviour, generally. Before implementing a pattern, therefore, ask whether the standard library, or a well-known library you already use, already provides it, in a form, that fits your problem, even if it is not labelled, with the pattern's name, in its documentation, which may describe it, in practical terms, such as, iterating, or managing resources, rather than, in pattern terminology, that a beginner, might not recognise, from the documentation alone, without the pattern vocabulary, to connect, the library feature, to the pattern idea, behind it, for that feature, specifically, rather than, generally, for all library features, at once, as a whole, over time, generally.
That connection, between patterns and library features, also works in reverse: when you design a library, or a module, for others to use, consider whether a familiar pattern, already embodied, in the standard library, would make your interface, more intuitive, for users, who already know, that pattern, from the library, itself, without needing, to learn, a new, idiosyncratic interface, for your module, specifically, rather than, generally, for all modules, at once, as a whole, over time, generally. An interface that follows, a familiar pattern, is easier, to learn, because users, can transfer, their existing knowledge, from the library, to your module, directly, without relearning, the same idea, in a new form, for your module, specifically, which is a significant, usability benefit, for a library, or module, intended, for wide use, by many developers, over a long lifetime, with many versions, and many users, depending on it, for their work, over time, generally. A library interface, that invents, a new mechanism, for a problem, that a standard pattern, already solves, well, in the library, itself, forces, every user, to learn, that new mechanism, from scratch, for no benefit, over the familiar pattern, which is a cost, in learning time, and in errors, that users, will pay, for the library's, idiosyncrasy, rather than, for its actual functionality, in practice, generally, and specifically, for each library, or module, designed, for others, to use, over its lifetime, as a whole, over time, generally.
Check: Why is recognising patterns inside the standard library useful before implementing one yourself?
Section 32
Real programs rarely use a single pattern in isolation, exactly, and because, a program's architecture, typically, combines, several patterns, each solving, a different part, in a way, that fits together, coherently, for the program, as a whole, over its run, with real data, and real requirements, for each part, of its design, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. A program might use, a factory, to create, objects, of different kinds, depending on configuration, a strategy, to choose, an algorithm, for processing, those objects, an observer, to notify, other parts, when processing, completes, and a command, to represent, each processing request, for queuing, or for undo, for the same, overall task, without any conflict, between patterns, because, each pattern, addresses, a different concern, and because, their structures, compose, naturally, of the program, as a whole, over its run, generally, and specifically, for each pattern, in the combination, where its role, is clear, and distinct, from the roles, of other patterns, over time, generally.
Combining patterns well requires keeping each pattern's role clear, which pattern, solves which problem, in the program, and can change, one pattern's part, without breaking, another pattern's part, unintentionally, through hidden coupling, between patterns, that were, meant to be, independent, as a whole, over its run, generally. That clarity is helped, by naming, patterns, in comments, or in documentation, where their use, is not obvious, from the code, alone, so that, a maintainer, reading the code, can recognise, the pattern, and recall, its intent, and its consequences, without having, to infer them, alone, which may be, obscured, by adaptations, made, for Python, that change, in ways, that are, sensible, for that program, but that make, the pattern, harder, to recognise, for a reader, who does not, already know, in that part, of the code, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally, and specifically, for each pattern, combined, in a program, where its role, should be, documented, briefly, for future maintainers, who will, work with that combination, over the program's lifetime, generally.
Combining patterns also requires restraint, many patterns, for every part, of its design, can become, harder to understand, than a program, that uses, fewer patterns, more selectively, for the parts, where they, genuinely help, and simpler designs, elsewhere, for parts, where patterns, are not needed, in practice, generally. A useful discipline, is to introduce, patterns, one at a time, as problems, arise, that call for them, specifically, rather than, designing, an entire architecture, around patterns, in advance, before knowing, which problems, will actually arise, in practice, for real users, with real data, and real workloads, that determine, which design problems, are real, and significant, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. That incremental approach, lets, each pattern, earn its place, by solving, a real problem, that has actually appeared, in the program's development, or its use, rather than, by anticipating, hypothetical problems, that may never, actually arise, in practice, for real users, of that program, specifically, over its lifetime, generally, and specifically, for each pattern, introduced, incrementally, as its problem, appears, in the program, over time, as a whole.
Check: Why should patterns be introduced one at a time as problems arise, rather than designed in all at once?
Section 33
Architectural patterns operate at a larger scale than the design patterns covered so far, over its run, generally. A well-known architectural pattern separates a program into three parts: a model, which presents data, to users, in a form, they can understand, or equivalent, which handles, user input, and coordinates, so that, each part, can change, independently, of the others, as far as, possible, over its lifetime, generally, and specifically, for each part, of the architecture, where separation, allows, data logic, presentation logic, and input handling, to evolve, separately, without forcing, changes, in one part, to ripple, of the program, as a whole, over its run, generally. That separation, is valuable, for programs, with user interfaces, where, presentation, changes, frequently, for usability, or for style, while, data rules, remain, relatively stable, over time, and where, coupling presentation, tightly, to data, would make, every presentation change, a data change, as well, with risk, of breaking, data correctness, for the sake, of appearance, in practice, generally.
Architectural separation also helps testing, in isolation, from the others, as far as, possible, for that part, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. A model, can be tested, by checking, its data rules, directly, through its interface, for data operations, alone, without rendering, any presentation, for a user, to see, during testing, which would, slow tests, and make them, fragile, to presentation changes, that should not, affect data correctness, in any way, for the program, as a whole, over its run, generally. A view, can be tested, with sample data, without needing, real data rules, to be, fully implemented, for the view, to render, correctly, for its purpose, in the program, as a whole, over its run, generally. That testability, is a major, practical benefit, of architectural separation, beyond, its conceptual clarity, because, it lets, teams, work, on different parts, in parallel, without blocking, each other, on incomplete parts, and because, it lets, tests, run, quickly, and reliably, without external dependencies, such as, user interfaces, or databases, that would, make them, slow, or flaky, if used, directly, in every test, run, generally, and specifically, for each part, tested, in isolation, through its interface, alone, without its collaborators, in practice, generally.
Architectural patterns, should be chosen, at a scale, appropriate, and lifetime, because, a full, three-part architecture, with a single, simple interface, is overkill, adding, layers, and indirection, without benefit, for a program, whose parts, are already, simple, and whose changes, are already, easy, to make, directly, without architectural separation, to protect them, from each other, in practice, generally. For a small program, a simple, direct structure, with a few, well-organised modules, is clearer, and easier, to maintain, imposed, from a textbook, without regard, or its actual, change patterns, over time, generally. For a large program, expected, to grow, and to be, maintained, by many developers, over years, architectural separation, becomes, essential, because, without it, changes, in one area, ripple, unpredictably, making, maintenance, increasingly difficult, and increasingly risky, as the program, grows, in size, and in complexity, over its lifetime, generally, and specifically, where architecture, must be, decided, deliberately, early, before growth, makes, restructuring, prohibitively expensive, in time, and in risk, for its users, and its developers, alike, over time, generally.
Check: How does separating model, view, and controller help both maintenance and testing?
Section 34
Anti-patterns are recurring design mistakes that look like solutions, harder to test, over time, as recognising, good patterns, because, an anti-pattern, adopted, unknowingly, can undo, the benefits, of good patterns, applied, elsewhere, as a whole, over its run, generally, and specifically, for each anti-pattern, in a program, where its harm, must be, recognised, before it, can be, corrected, through refactoring, for that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. One common anti-pattern, a single class, or module, that tries, to do everything, in a program, accumulating, responsibilities, for data, for presentation, for input, for storage, that grows, larger, and harder, to understand, added, to the program, over its lifetime, generally, until, no single developer, can understand, the whole object, anymore, without weeks, of study, and until, changing, any part, of it, risks, breaking, unrelated parts, that share, its code, and its state, through the same, monolithic object, in practice, generally, and specifically, in a program, where its size, and its responsibilities, have grown, beyond what, a single object, can handle, coherently, for the program, as a whole, over its run, generally.
Another anti-pattern, is spaghetti code, where, control flow, jumps, unpredictably, between parts, of a program, through tangled, direct links, and shared, global state, without any, clear structure, to guide, a reader, through what happens, in what order, in the program, as a whole, over its run, generally. Spaghetti code, often, arises, gradually, from quick fixes, that add, a special case, here, and a workaround, there, without refactoring, the overall structure, to accommodate, new requirements, cleanly, as they arise, over time, and during, its maintenance, by developers, under deadline pressure, who choose, the fastest, local fix, over a cleaner, structural change, that would, take longer, but that would, preserve, the program's structure, for future changes, generally, and specifically, that contributes, to spaghetti, over time, as a program, grows, through many, such fixes, without structural, refactoring, to keep, its design, coherent, for its readers, and its maintainers, alike, over its lifetime, generally.
A third anti-pattern, is copy-and-paste programming, where, similar code, is duplicated, in many places, rather than, extracted, or class, that all places, can use, together, so that, a bug, must be, fixed, in every copy, individually, leaves, the bug, in place, for some uses, of that code, in the program, as a whole, over its run, generally. Copy-and-paste, often, starts, innocently, to reuse, working code, without taking, the time, to generalise it, properly, into a shared abstraction, that covers, both uses, cleanly, without duplication, for future maintainers, who must, keep all copies, in sync, as requirements, change, over time, during the program's evolution, and during, its maintenance, generally. Recognising, these anti-patterns, early, through code review, and through, a habit, of noticing, when a class, grows too large, when control flow, becomes tangled, or when, code, is duplicated, across a program, allows, a team, to refactor, toward better designs, before anti-patterns, become, entrenched, and hard, to remove, without a major, risky rewrite, of the affected parts, of the program, as a whole, over its lifetime, generally, and specifically, for each anti-pattern, detected, early, through review, and through, disciplined attention, to design quality, in everyday development, work, generally.
Check: How does a god object make a program harder to change safely over time?
Section 35
Refactoring toward a pattern means restructuring existing code, gradually, from scratch, risky change, that could, introduce bugs, while, design, generally, and specifically, preserve, behaviour, and allow, tests, to verify, that behaviour, specifically, rather than, generally, at once, over time, generally. The process, typically, starts, by recognising, such as, between objects, that matches, as described, specifically, rather than, generally, at once, over time, generally. Next, introduce, such as, extracting, creation logic, or extracting, an interaction, while, keeping, existing behaviour, unchanged, being refactored, generally. Then, run, tests, to verify, that behaviour, is unchanged, before, proceeding, sequence, as intended, for that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally.
That incremental approach, is safer, than a big-bang, rewrite, because, each step, is small, and verifiable, and because, a problem, introduced, by a step, can be, traced, to that step, specifically, rather than, with many possible, causes, for the problem, in question, generally, and specifically, for each step, in a refactoring, sequence, toward a pattern, in a program, where small steps, and frequent tests, provide, a safety net, that large rewrites, lack, for their changes, as a whole, over their duration, generally. It also, allows, refactoring, to be, paused, or abandoned, if, it becomes clear, during the process, that the pattern, for that code, as much, as expected, when it was, first considered, for that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. A partially refactored, code, that still works, correctly, is far better, than a half-finished, rewrite, at all, and that must, either be, completed, under pressure, or reverted, at a cost, in time, and in frustration, for developers, who undertook it, with good intentions, but without, incremental safety, to protect, their work, as it progressed, step by step, toward the pattern, as intended, for that code, specifically, rather than, generally, for all code, at once, as a whole, over time, generally.
Refactoring, away from, a pattern, is equally, important, and equally, legitimate, because, a pattern, that was, useful, when introduced, may become, unnecessary, as requirements, change, become, sufficient, for a program's, current needs, in practice, for real users, with real requirements, that determine, is still, justified, for that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. Removing, a pattern, that no longer, earns its complexity, simplifies, code, for future maintainers, who need not, understand, pattern machinery, that serves, no current purpose, for the program, as a whole, over its run, generally. Whether, refactoring, toward, or away from, a pattern, the discipline, in small steps, verify, behaviour, after each step, through tests, and through, careful review, of the change, for that step, specifically, rather than, generally, for all steps, at once, as a whole, over time, generally, and stop, when, the code, is clear, and the pattern, either, serves its purpose, or has been, removed, cleanly, without breaking, behaviour, for users, for their work, in the program, as a whole, over its run, generally, and specifically, for each refactoring, toward, or away from, a pattern, in a program, over its lifetime, as it evolves, through maintenance, and through, changing requirements, generally.
Check: Why is refactoring toward a pattern safer in small, tested steps than in one large rewrite?
Section 36
Concurrency patterns address how programs handle work that happens, to happen, through interleaving, of tasks, or through, parallel execution, as discussed, on concurrency, generally, where threading, were introduced, as concepts, on either, specifically, rather than, generally, topics, at once, over time, generally. Patterns, for concurrency, build, by providing, reusable structures, for common, concurrent problems, such as, coordinating, access, so that, concurrent tasks, it, through simultaneous, conflicting updates, that leave, the data, assuming, in the program, as a whole, over its run, generally, and specifically, where coordination, is essential, for correctness, and for reliability, over its run, that determine, whether coordination, succeeds, or fails, for that program, specifically, rather than, generally, at once, as a whole, over time, generally.
One common, concurrent pattern, where, a fixed number, of worker tasks, process, jobs, in order, as they become, available, for processing, in the pool, at that time, during execution, with real jobs, and real workers, that determine, throughput, for the program, as a whole, over its run, generally. A pool, limits, concurrency, preventing, a program, from creating, an unbounded number, of tasks, of jobs, which would, exhaust resources, such as, memory, or connections, to external systems, that have, finite limits, on how many, concurrent uses, they can support, safely, with given resources, that constrain, its concurrency, in practice, generally. A pool, also, reuses, workers, rather than, creating, a new task, for every job, which, saves, the overhead, of task creation, for each job, individually, in a program, that processes, many small jobs, over its run, with real workloads, that determine, whether task creation, overhead, matters, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and specifically, for each pool, where reuse, and limits, provide, both efficiency, and safety, for concurrent processing, of jobs, from a queue, in order, as workers, become free, to process them, generally.
Another, concurrent pattern, is the producer-consumer, structure, where, one set, of tasks, produces, items, and places them, or queue, while, another set, of tasks, consumes, items, from that buffer, for further processing, with coordination, to ensure, that producers, do not overflow, the buffer, when consumers, are slow, and that, consumers, do not read, when producers, are slow, or temporarily, idle, of the program, with given producers, and given consumers, that determine, the flow, of items, through the buffer, as a whole, over its run, generally. That coordination, typically, involves, limits, on buffer size, and waiting, mechanisms, that pause, producers, when the buffer, is full, and pause, consumers, so that, neither side, wastes, resources, through busy waiting, or polling, for a condition, become true, in the buffer, specifically, rather than, generally, for all buffers, at once, as a whole, over time, generally. These, concurrent patterns, like, all patterns, in this chapter, should be, applied, only where, concurrency, is genuinely needed, for a program, and where, measurement, shows, that sequential processing, is insufficient, for its performance, or responsiveness, requirements, in practice, for real users, with real workloads, that determine, whether concurrency, helps, or merely, adds complexity, and risk, of concurrent bugs, that are, notoriously hard, to reproduce, and to fix, compared with, sequential bugs, that occur, deterministically, for given inputs, of the program, as a whole, over its lifetime, generally.
Check: How does a worker pool limit concurrency to a manageable level?
Section 37
Data processing patterns describe how programs transform data, from input, to output, of stages, performs, one transformation, understood, rather than, monolithic transformation, that does, everything, at once, that is, over time, as requirements, for processing, evolve, are added, are modified, or removed, and during, its maintenance, by developers, who must, understandable, in stages, generally, and specifically, is clear, and distinct, generally. A pipeline, for example, might, read data, filter out, unwanted records, convert, remaining records, aggregate, them, and write, a summary, implemented, function, or component, that takes, data, and produces, data, to consume, in turn, specifically, rather than, generally, at once, over time, generally.
Pipelines, help testing, because, each stage, can be, tested, in isolation, specifically, without needing, to run, generally. A filtering stage, can be, tested, with records, that should, pass, and records, that should, for that stage, specifically, without involving, reading, or writing, stages, that are, irrelevant, to filtering logic, alone, in that test, specifically, rather than, generally, for all stages, at once, as a whole, over time, generally. A conversion stage, can be, tested, with records, in old form, and expected, records, in new form, for that conversion, specifically, without involving, aggregation, or output, stages, that are, irrelevant, to conversion logic, alone, in that test, specifically, rather than, generally, for all stages, at once, as a whole, over time, generally. That isolation, makes, tests, faster, and more focused, because, a failing test, points, directly, to the stage, that failed, for that test, specifically, rather than, to the pipeline, as a whole, where, the failing stage, must be, identified, through debugging, across many stages, that could, each have caused, the failure, in question, generally, and specifically, for each test, where isolation, matters, for reliable, repeatable results, over time, generally.
Pipelines, also, help, with performance, and with memory, because, stages, can process, data, incrementally, as it flows, through them, rather than, loading, all data, into memory, at once, that must, hold everything, before producing, any output, for the program, as a whole, over its run, generally. Incremental processing, through a pipeline, using, generators, or streams, as discussed, for data processing, generally, allows, a program, to handle, large data, that would not, fit in memory, at once, by processing, it, piece by piece, through each stage, in turn, without ever, holding, the whole dataset, in memory, during execution, with real data, and real volumes, that determine, whether in-memory, processing, is feasible, at all, during its run, generally, and specifically, for each pipeline, where incremental processing, matters, for memory, and for performance, over its run, with real data, and real volumes, generally. Pipelines, should be, kept, simple, however, because, a pipeline, with too many, tiny stages, can become, as hard, to follow, as a monolith, with readers, forced, to jump, between many, small stages, to understand, an overall transformation, that could, have been, expressed, more clearly, in fewer, larger stages, each handling, a coherent part, of the transformation, as a whole, rather than, fragmenting it, into pieces, so small, is lost, in the pipeline's, overall flow, for readers, who must, understand, the transformation, as a whole, to maintain it, safely, generally, and specifically, for each pipeline, where stage size, must be, balanced, between isolation, for testing, and coherence, for reading, over time, generally.
Check: How does a pipeline help both testing and memory use for large data?
Section 38
Error handling patterns describe how programs deal, with failures, rather than, scattering, error checks, throughout code, that is, and easy, among many, where errors, can occur, that determine, succeeds, or fails, during execution, that may, fail, unpredictably, for reasons, generally, and specifically, matters, for correctness, generally. A common pattern, errors, where they, can be, dealt with, meaningfully, rather than, through which, an error, passes, specifically, rather than, generally, at once, over time, generally. Low-level code, that detects, an error, such as, often, cannot decide, about it, beyond, reporting it, upward, to code, that knows, decide, a fallback, the failure, and what, the user, can do, about it, generally, and specifically, handled, where handling, is centralised, of error, rather than, scattered, to handling, generally.
Another pattern, result objects, that represent, either success, or failure, instead of, raising, an exception, when, failure, expected outcome, such as, searching, or validating, user input, invalid, for reasons, that are, rather than, exceptional conditions, that indicate, a bug, specifically, rather than, generally, at once, over time, generally. Result objects, make, failure, explicit, so that, callers, must handle, both success, and failure, cases, specifically, rather than, assuming success, and being, surprised, when failure, actually occurs, during execution, that determine, succeeds, or fails, specifically, rather than, generally, at once, as a whole, over time, generally. That explicitness, is similar, to the benefit, of Optional, discussed earlier, for values, where, the type, forces callers, to consider, absence, rather than, assuming presence, and failing, when absence, actually occurs, with given data, of the program, as a whole, over its lifetime, generally, and specifically, for each operation, where failure, is expected, and should, be handled, explicitly, by callers, rather than, through exceptions, that may be, forgotten, or mishandled, in client code, that uses, that operation, for its purpose, in the program, as a whole, over its run, generally.
Error handling, should also, distinguish, between errors, that a program, can recover from, such as, that may, succeed, on retry, for the network, to recover, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally, and errors, that it cannot, recover from, such as, corrupted data, that cannot, be repaired, by retrying, the same operation, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally. Recoverable errors, should be, retried, or worked around, with a fallback, where appropriate, for the operation, in question, generally, while, unrecoverable errors, should be, reported, clearly, and the program, should fail, safely, without corrupting, further data, or misleading, users, about what happened, for their work, in the program, as a whole, over its run, generally. Logging, errors, with enough context, to diagnose, them later, without logging, sensitive data, that should not, be recorded, is part, as discussed, for security, generally, where sensitive information, must not, be exposed, through logs, or error messages, that users, or attackers, could, read, for purposes, beyond diagnosing, the error, itself, in practice, generally, and specifically, for each error, logged, in a program, where its context, must be, sufficient, for diagnosis, without being, excessive, in sensitive detail, that risks, exposure, for that error, specifically, rather than, generally, for all errors, at once, as a whole, over time, generally.
Check: Why should errors be handled at the level where they can be dealt with meaningfully?
Section 39
Documenting pattern use in code helps future maintainers understand why a structure exists, adapted, for Python, in ways, that change, specifically, rather than, generally, at once, over time, generally. A brief comment, and stating, the problem, it solves, specifically, rather than, generally, at once, over time, generally, gives, a maintainer, for understanding, the structure, without having, to infer, the pattern, alone, obscured, by adaptations, made, for Python, that change, its form, it harder, to recognise, already know, specifically, rather than, generally, at once, over time, generally, and specifically, documented, in code, should be, stated, briefly, who will, generally.
Documentation, for patterns, should focus, on intent, rather than, on mechanics, because, mechanics, can be, read, itself, who understands, the language, in which, generally, while, intent, why, this structure, was chosen, for this problem, in this program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, cannot be, read, from code, alone, without additional, context, about the problem, and the alternatives, considered, during design, for that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally. A comment, that says, that a factory, is used, because, object kinds, depend on configuration, at run time, and that, direct construction, would couple, callers, to that decision, in detail, for every kind, of object, created, through the factory, over time, generally, tells, a maintainer, far more, than a comment, that merely, restates, what the factory, does, mechanically, step by step, in its code, with given configuration, that determine, what it creates, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally, and specifically, for each pattern, documented, by intent, rather than, by mechanics, alone, in a program, over its lifetime, as it evolves, through maintenance, and refactoring, generally.
Pattern documentation, should also, be kept, brief, because, long explanations, in every place, in a program, as a whole, over its run, generally, add clutter, without benefit, for readers, who already, know the pattern, from its name, alone, and who need, only, the program-specific reason, for its use, in that place, specifically, rather than, generally, for all places, at once, as a whole, over time, generally. A single sentence, naming the pattern, is usually, sufficient, for a maintainer, who knows, the pattern, in general, and who needs, only, to know, why it applies, here, in this program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. For patterns, that are, less well known, to the team, or that are, applied, for a problem, that does not, in that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, or a reference, to team documentation, explaining, the pattern, and its use, in that program, specifically, may be, justified, for future maintainers, who will, encounter it, in that code, and who need, more context, to understand it, correctly, for their work, on that code, over its lifetime, generally, and specifically, documented, in code, as appropriate, for its familiarity, to the team, and its unusualness, in its application, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally.
Check: Why should pattern documentation focus on intent rather than mechanics?
Section 40
Knowing when to invent a new solution, that balances, familiarity, against fit, because, brings, and proven, structure, will recognise, quickly, in code, generally, while, tailored, may fit, that problem, more precisely, does, specifically, rather than, generally, at once, over time, generally. In most cases, should be, preferred, the problem, reasonably well, even if, specifically, rather than, generally, at once, over time, generally, because, its familiarity, outweighs, small imperfections, in fit, accommodated, specifically, without changing, in practice, generally, and specifically, applied, is good enough, to justify, its use, over a new, unfamiliar solution, that would, require, every future maintainer, to learn, from scratch, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally.
Inventing, a new solution, is justified, when, no known pattern, fits, the problem, well, or when, in complexity, or in indirection, outweigh, its benefits, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, and when, a simpler, direct solution, tailored, to the problem's, actual details, serves, better, than a pattern, forced, onto a problem, it does not, naturally fit, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. In that case, the new solution, should be, documented, clearly, with its intent, and its reasoning, as discussed, for pattern documentation, generally, where intent, matters more, than mechanics, for future maintainers, who must, understand, was chosen, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, so that, they can judge, remains, appropriate, as requirements, change, over time, and during, its maintenance, by developers, who must, understandable, despite its novelty, for future readers, who encounter it, in that code, and who need, context, to understand it, correctly, for their work, on that code, over its lifetime, generally, and specifically, invented, for a problem, fit well, in practice, generally.
Finally, remember, that patterns, are a means, to good design, not, an end, in themselves, and that, a program, whose design, is clear, simple, and well-tested, without, any named patterns, is better, than a program, whose design, is cluttered, with patterns, applied, rather than, for real problems, that they solve, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. The goal, of learning, patterns, is not, to use, as many, as possible, in every program, you write, from now on, generally, but to, recognise, design problems, more quickly, when they arise, in real programs, with real requirements, and real users, over time, generally, and to, have, a repertoire, of proven solutions, to draw on, when a problem, matches, a pattern, you know, well, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally. That repertoire, combined, with judgement, about when, to use, a pattern, when, to simplify, it, for Python, and when, to invent, a simpler, direct solution, instead, that this chapter, aims, to develop, for your work, with patterns, as tools, in your practice, from now on, generally, and specifically, for each program, you will write, or maintain, with patterns, as appropriate, over its lifetime, as a whole, over time, generally.
Check: When is inventing a new solution justified instead of using a known pattern?
Section 41
Code smells are surface signs that a design may have a deeper problem, should be, reconsidered, specifically, rather than, generally, at once, over time, generally. A long method, that does, hard-to-follow sequence, of steps, suggesting, should be, split, each handling, one responsibility, clearly, specifically, rather than, generally, at once, over time, generally. A class, with many, unrelated methods, suggesting, responsibilities, split, each with, specifically, rather than, generally, at once, over time, generally. Duplicated code, suggesting, should be, extracted, to replace, the duplicates, maintained implementation, share, together, specifically, rather than, generally, for all code, at once, as a whole, over time, generally.
Smells do not prove, that a design, is wrong, because, a long method, may be, long, such as, a complex algorithm, whose steps, belong together, for readability, as a single, coherent sequence, that would, be harder, to follow, if split, into many, tiny methods, whose individual meaning, is lost, for readers, who must, understand, the algorithm, as a whole, to maintain it, safely, over its lifetime, generally. A class, with many methods, may be, a legitimate, central coordinator, for a subsystem, whose methods, belong together, for that subsystem, specifically, rather than, generally, for all subsystems, at once, as a whole, over time, generally. Duplicated code, may be, a deliberate, temporary duplication, during exploration, before, a shared abstraction, is clear, enough, to extract, safely, without forcing, a premature generalisation, that later, proves wrong, in practice, generally, and specifically, for each duplication, where premature extraction, would be, worse, than temporary duplication, for that code, specifically, rather than, generally, at once, as a whole, over time, generally. Smells, therefore, should prompt, investigation, not automatic, refactoring, because, the investigation, may reveal, a good reason, in that part, of the program, specifically, rather than, generally, for all parts, at once, as a whole, over time, generally, and because, refactoring, without understanding, the reason, may make, things worse, by breaking, a design, that was, actually sound, for its purpose, in practice, generally.
When, investigation, confirms, that a smell, indicates, a pattern, often, provides, the refactoring, direction, for fixing it, specifically, rather than, generally, for all smells, at once, as a whole, over time, generally. Duplicated creation logic, points, toward a factory, that centralises, creation, in one place, for all uses, together, for that code, specifically, rather than, generally, at once, as a whole, over time, generally. Tangled interaction, between objects, points, toward a mediator, that centralises, interaction rules, in one place, together, for that group, specifically, rather than, generally, for all groups, at once, as a whole, over time, generally. A proliferation, of subclasses, for combinations, of features, points, toward decorator, that composes, features, dynamically, without a subclass, for each combination, of features, in practice, generally, and specifically, for each combination, in a program, where decorator, provides, flexible composition, without subclass explosion, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Using, smells, as prompts, for pattern-guided refactoring, in this way, connects, the two ideas, of recognising, problems, and applying, proven solutions, to them, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, and it, makes, refactoring, purposeful, rather than, cosmetic, for a program, as a whole, over its lifetime, generally.
Check: Why should a code smell prompt investigation rather than automatic refactoring?
Section 42
A pattern catalogue is a collection of patterns, organised, so that, programmers, can find, the pattern, they need, without having, to know, its name, in advance, from memory, alone, which is, unrealistic, can memorise, completely, in practice, generally, and specifically, helps, by organising, patterns, they solve, rather than, alone, which are, only useful, know them, where they, were applied, successfully, generally, and specifically, should be, stated, clearly, who encounter, that problem, to find, for it, quickly, without reinventing, it, from scratch, specifically, rather than, generally, at once, over time, generally.
Catalogues, typically, organise, patterns, by category, such as, creational, structural, and behavioural, as used, in this chapter, generally, for its organisation, of patterns, into groups, by the kind, of problem, they solve, for a program, as a whole, over its run, generally. Within, each category, patterns, are described, including, the problem, they solve, the context, in which, that problem, arises, for real programs, with real requirements, and real constraints, that determine, whether the pattern, fits, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, the solution's structure, in general terms, without tying, it, that would, limit its applicability, to programs, written, in that language, alone, for that implementation, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and the consequences, of applying, the pattern, including, its benefits, and its costs, in complexity, in flexibility, and in testability, for a program, that adopts it, for its problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally.
Learning, to use, a catalogue, effectively, involves, learning, to describe, your problem, in terms, of its forces, that is, the competing, requirements, and constraints, that pull, a design, in different directions, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, because, patterns, are indexed, by the problems, and forces, they address, more than, by their names, alone, which are, only useful, after you, already know, the pattern, from prior study, or prior use, in a program, where it, was applied, successfully, in the past, generally. When, you face, a design problem, try, to state, its forces, plainly: what, must be, flexible, and what, must be, stable, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, what, must be, easy, to change, and what, must be, protected, from change, for that problem, specifically, and what, trade-offs, are acceptable, for that problem, in practice, for real users, with real requirements, that determine, which trade-offs, matter, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Then, look, for patterns, whose problem, and forces, match, yours, closely, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, and evaluate, those patterns, by their consequences, for your program, specifically, rather than, by their popularity, or their familiarity, to you, personally, from prior use, in other programs, with other problems, and other forces, that may, not match, yours, for this problem, in this program, at this time, during its development, or its maintenance, as a whole, over its lifetime, generally.
Check: How should you use a pattern catalogue when you do not already know a pattern's name?
Section 43
Patterns appear in client libraries, because, those libraries, must solve, recurring problems, such as, authenticating, requests, retrying, failed requests, and paging, through large, result sets, that is, consistent, and easy, client programs, to use, correctly, reinventing, solutions, individually, specifically, rather than, generally, at once, over time, generally. A client library, that provides, a single, simple interface, is, in effect, a facade, hiding, well-chosen operations, that cover, common uses, specifically, rather than, generally, at once, over time, generally. A library, that returns, instead of, raising, an exception, is, using, generally, where failure, is explicit, specifically, rather than, generally, at once, over time, generally. Recognising, these patterns, in libraries, you use, helps you, more effectively, because, you can, predict, how they, will behave, based on, the pattern, they follow, specifically, rather than, generally, at once, over time, generally.
Client libraries, also, illustrate, the importance, because, a library, used, cannot, change, its interface, lightly, without breaking, those clients, with the service, in question, generally, and specifically, for each client, affected, in a library, it depends on, for its operation, as a whole, over its run, generally. Patterns, help, library authors, design, stable interfaces, by separating, what varies, from what, remains stable, for a library, over its versions, as a whole, over its lifetime, generally. For example, a library, that accepts, a strategy, for retrying, failed requests, as a parameter, allows, clients, to customise, retry behaviour, without changing, for making requests, in general, for that service, specifically, rather than, generally, for all services, at once, as a whole, over time, generally. A library, that provides, a builder, for constructing, complex requests, with many, optional parameters, allows, clients, to build, requests, step by step, without needing, a constructor, with many, positional arguments, that are, hard to remember, in order, with many options, that interact, with each other, in ways, that a builder, can validate, as steps, are added, during construction, of the request, specifically, rather than, generally, for all requests, at once, as a whole, over time, generally.
When, you design, a library, or a module, for others, to use, therefore, consider, patterns, not only, as internal, design tools, for your implementation, alone, but also, as interface, design tools, for your users, who will, interact, with your library, alone, without seeing, its internal implementation, in detail, for their work, with it, in practice, generally, and specifically, in a library, where patterns, can make, the interface, more intuitive, for users, who already, know those patterns, from other libraries, they have used, in the past, for similar purposes, in their work, generally. An interface, that follows, familiar patterns, is easier, to learn, because, users, can transfer, their existing knowledge, from other libraries, to yours, directly, without relearning, the same ideas, for your library, specifically, rather than, generally, for all libraries, at once, as a whole, over time, generally. That transfer, of knowledge, is a significant, usability benefit, for a library, intended, for wide use, by many developers, with many versions, and many users, depending on it, for their work, over time, generally, and specifically, for each library, designed, for others, to use, where pattern familiarity, should be, considered, as part, of interface design, alongside, correctness, and performance, for that library, as a whole, over its lifetime, generally.
Check: How do patterns help library authors design stable, learnable interfaces?
Section 44
Configuration patterns describe how programs obtain, and use, settings, that vary, between environments, without hard-coding, those settings, into code, where they, would be, and hard, to keep, separate, from code, itself, specifically, rather than, generally, at once, over time, generally. A common pattern, configuration, such as, files, at startup, configuration object, can use, without knowing, came from, specifically, rather than, generally, at once, over time, generally. That centralisation, makes, configuration, easier, to manage, because, settings, in code, even though, their values, come from, that vary, by environment, specifically, rather than, generally, at once, over time, generally, and because, changing, a setting, does not, require, changing, code, itself, specifically, rather than, generally, at once, over time, generally.
Configuration, should be, validated, at startup, as discussed, generally, must be, checked, a program, before it, spreads, that assumes, its validity, based on, hints, alone, which cannot, guarantee, anything, has never seen, of any type, or shape, when it arrives, in practice, for real programs, interacting, over time, generally. Validating, configuration, early, means, that, a program, with invalid settings, fails, quickly, about which setting, is invalid, and why, for that setting, specifically, rather than, generally, for all settings, at once, as a whole, over time, generally, instead of, running, before failing, mysteriously, is finally, used, for an operation, far from startup, where its invalidity, originated, in configuration, loaded, at the start, generally, and specifically, for each setting, validated, at startup, of the program, with given configuration, and given environment, that determine, which settings, are valid, for that run, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally. Early validation, also, makes, configuration errors, easier, to diagnose, for operators, who deploy, a program, and who need, clear feedback, about what, is wrong, with their settings, for that environment, specifically, rather than, generally, for all environments, at once, as a whole, over time, generally.
Sensitive configuration, such as, passwords, or keys, for external services, deserves, special care, as discussed, for security, generally, where secrets, must not, be hard-coded, into code, or into, version control, where they, could be, exposed, to anyone, with access, to the code, or its history, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Instead, sensitive settings, should be, supplied, through secure, external mechanisms, that the program, reads, at startup, without storing, them, in code, itself, for any deployment, of the program, with any configuration, and any environment, that it, runs in, generally, and specifically, in a program, where its handling, must be, secure, by design, rather than, by hope, that it, will not be, exposed, through code, or through logs, or through, error messages, that users, or attackers, could, read, for purposes, beyond diagnosing, configuration errors, themselves, in practice, generally. That secure handling, is part, of configuration design, not, an afterthought, to be, added, after a program, is already, deployed, with secrets, hard-coded, into its code, or its history, where they, are already, exposed, and must, be rotated, to restore, security, for the program, as a whole, over its run, generally, and specifically, for each secret, exposed, through poor configuration, handling, in a program, over its lifetime, as it evolves, through maintenance, and through, changing deployment, practices, generally.
Check: Why should configuration be validated at startup rather than when each setting is first used?
Section 45
Plugin patterns let a program be extended, without changing, by defining, for plugins, for discovering, and loading, them, or packages, that implement, that interface, extension, specifically, rather than, generally, at once, over time, generally. A classic example, that supports, plugins, where, each plugin, implements, or its tool, and where, the editor, discovers, plugins, at startup, and loads, them, without needing, to know, in advance, which plugins, will be, available, of the editor, with given plugins, installed, for their work, with the editor, as a whole, over its run, generally, and specifically, for each plugin, discovered, and loaded, for that run, of the editor, with those plugins, specifically, rather than, generally, for all plugins, at once, as a whole, over time, generally.
A plugin interface, should be, as small, and as stable, as possible, because, every method, or attribute, in the interface, is a commitment, to plugin authors, that the program, will continue, to support, that method, or attribute, in future versions, of the program, as a whole, over its lifetime, generally, and because, a large, unstable interface, makes, plugin development, harder, and makes, breaking changes, more likely, for plugin authors, who depend, on that interface, for their plugins, specifically, rather than, generally, for all plugins, at once, as a whole, over time, generally. A small interface, with a few, well-chosen operations, that cover, common plugin needs, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, is easier, for plugin authors, to implement, correctly, and easier, for the program, to maintain, compatibly, across versions, as it evolves, over its lifetime, generally, and specifically, for each plugin, written, against that interface, over time, as the program, and its plugins, evolve, together, through maintenance, and through, changing requirements, for the program, as a whole, over its run, generally.
Plugin discovery, should also, be, safe, because, loading, code, from external sources, at run time, can, introduce, security risks, if, plugins, are loaded, from untrusted locations, or without, any verification, of their origin, of the program, with given plugins, installed, for their work, with the program, as a whole, over its run, generally, and specifically, for each plugin, loaded, where its trustworthiness, must be, considered, before loading, it, with full access, and its resources, for that run, specifically, rather than, generally, for all plugins, at once, as a whole, over time, generally. For programs, where, plugins, are installed, deliberately, by users, who trust, their source, with the program, as a whole, over its run, generally, discovery, is usually, sufficient, for safety, in practice, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. For programs, where, plugins, could come, from untrusted sources, additional, safeguards, such as, restricting, what plugins, can do, or verifying, their origin, before loading, them, should be, considered, as part, of plugin design, from the start, rather than, as an afterthought, has already, occurred, loaded, without safeguards, with access, to sensitive data, or system resources, that it, should not, have had, for its purpose, in the program, as a whole, over its run, generally, and specifically, for each plugin, loaded, where safeguards, matter, for security, in the program, as a whole, over its lifetime, generally.
Check: Why should a plugin interface be kept small and stable over time?
Section 46
Caching, stores, results, so that, later requests, can be, served, without repeating, itself, specifically, rather than, generally, at once, over time, generally. Caching, is discussed, in detail, on performance, generally, were covered, for programs, whose performance, depends, on avoiding, repeated work, generally, and specifically, where caching, provides, a measurable, performance benefit, specifically, rather than, generally, at once, over time, generally. As a pattern, caching, involves, more than, merely storing, a result, because, it also, involves, deciding, and when, recomputed, because, underlying data, has changed, was cached, specifically, rather than, generally, at once, over time, generally, which is, that makes, caching, harder, appears, reflected, accurately, for users, generally.
Invalidation, strategies, vary, by program, because, some data, changes, rarely, cached, safely, specifically, rather than, generally, at once, over time, generally, while, other data, changes, frequently, cached, only briefly, specifically, rather than, generally, at once, as a whole, over time, generally, depending on, how often, the data, actually changes, in practice, for real users, with real workloads, that determine, the safe, cache lifetime, for that data, specifically, rather than, generally, for all data, at once, as a whole, over time, generally. Time-based invalidation, where, a cached result, expires, is simple, and works, when, slightly stale data, is acceptable, has changed, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally. Event-based invalidation, where, a cached result, is discarded, changes, is more precise, but more complex, because, it requires, tracking, which cached results, depend on, which data, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and because, a missed, invalidation event, leaves, stale data, in the cache, for users, who will, then see, outdated results, for their work, with the program, as a whole, over its run, generally, and specifically, whose invalidation, was missed, specifically, rather than, generally, at once, as a whole, over time, generally.
Caching, should be, applied, selectively, to operations, where measurement, shows, that repeated work, is actually, a bottleneck, for a program, as discussed, for performance work, generally, where optimisation, should follow, measurement, rather than, speculation, about a problem, in practice, for real data, and real workloads, that the program, actually handles, in production, or in testing, with realistic data, and realistic volumes, that reveal, whether repeated work, is truly, a constraint, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Caching, everything, indiscriminately, wastes, memory, on results, that are, rarely reused, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and risks, serving, stale data, for operations, where freshness, matters, for correctness, as a whole, over its run, generally. A cache, should also, have, a size limit, as discussed, for unbounded growth, generally, can exhaust, memory, of the program, with many, distinct inputs, that each, to the cache, without ever, removing, old entries, that will never, be reused, again, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and specifically, for each cache, in a program, where its size, must be, bounded, to prevent, memory exhaustion, over its run, with real data, and real volumes, generally.
Check: Why is cache invalidation the hardest part of caching as a pattern?
Section 47
Retry and timeout patterns deal with operations that may fail, temporarily, such as, network requests, that fail, connectivity problem, that will, succeed, if retried, to clear, specifically, rather than, generally, at once, over time, generally. A retry pattern, specifies, an operation, should be, retried, after failure, to wait, between retries, specifically, rather than, generally, at once, over time, generally, because, retrying, indefinitely, can waste, resources, and can, delay, a program, indefinitely, while waiting, succeed, rather than, specifically, rather than, generally, at once, over time, generally, and because, retrying, too aggressively, between retries, can, overwhelm, further, making, its recovery, harder, together, specifically, rather than, generally, at once, over time, generally.
A common, retry approach, progressively longer, between retries, so that, a service, under load, gets, time, to recover, arrives, specifically, rather than, generally, at once, over time, generally, a program, does not, flood, with rapid, repeated requests, that add, without helping, it recover, as a whole, over their runs, generally. That progressive waiting, should be, combined, with a limit, on total retries, spent retrying, for an operation, so that, a program, eventually, gives up, and reports, failure, to its caller, and what, the user, can do, about it, for their work, with the program, as a whole, over its run, generally, and specifically, for each operation, retried, must be, chosen, based on, how likely, temporary failure, is, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally, and on, how important, that operation, is, in practice, for real users, with real requirements, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally.
Timeouts, are the companion, to retries, because, an operation, that never, completes, neither succeeding, nor failing, clearly, with given inputs, and given conditions, that determine, its outcome, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally, can block, a program, indefinitely, while waiting, for a response, that will never, arrive, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally. A timeout, specifies, how long, to wait, for an operation, before giving up, and treating it, as failed, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally, so that, a program, can, retry, the operation, or report, failure, promptly, rather than, hanging, indefinitely, for a response, that may never, come, for that operation, in that run, of the program, with those inputs, and those conditions, specifically, rather than, generally, for all runs, at once, as a whole, over time, generally. Choosing, a timeout, involves, a trade-off, between, giving, an operation, enough time, to succeed, under normal, slow conditions, for that operation, specifically, rather than, generally, for all operations, at once, as a whole, over time, generally, and failing, promptly, when an operation, is truly, stuck, for that call, specifically, rather than, generally, for all calls, at once, as a whole, over time, generally, based on, the operation's normal, duration, for real data, and real conditions, that determine, its typical timing, in practice, generally, and specifically, for each operation, where a timeout, is set, for its calls, generally.
Check: Why should retries use progressively longer waits rather than immediate retries?
Section 48
Reviewing pattern use in other people's code is a skill, that improves, with practice, because, patterns, are often, adapted, in ways, that change, specifically, rather than, generally, at once, over time, generally, and because, a reviewer, must judge, whether, an adaptation, preserves, specifically, rather than, generally, at once, over time, generally, or whether, it has, into something, solves, its problem, reliably, specifically, rather than, generally, at once, over time, generally. A reviewer, should first, identify, which pattern, if any, the code, is trying, to use, specifically, rather than, generally, at once, over time, generally, by looking, structure, such as, a factory, that creates, objects, list, that notifies, registered objects, specifically, rather than, generally, at once, over time, generally, and then, ask, whether, that pattern, fits, the problem, it is, specifically, rather than, generally, at once, over time, generally.
Next, a reviewer, should check, whether, the pattern, is applied, consistently, throughout, the code, specifically, rather than, generally, at once, over time, generally, because, inconsistent application, where, some uses, and others, bypass it, generally, undermines, specifically, rather than, generally, at once, over time, generally. For example, if, goes, but, some creation, bypasses it, directly, of object, generally, then, a change, will not, affect, leading, to inconsistencies, between objects, created, and objects, created, directly, generally, and specifically, must be, understood, before it, can be, corrected, through refactoring, toward consistent, factory use, generally. Bypasses, may be, justified, specific, rather than, generally, at once, over time, generally, but, documented, as exceptions, with their reasons, stated, briefly, for future maintainers, who will, encounter them, in code, that otherwise, follows the pattern, consistently, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally.
Finally, a reviewer, should consider, whether, the pattern, is still, needed, for that code, as it, has evolved, over time, and during, its maintenance, by developers, who must, keep the pattern, relevant, to current requirements, for that code, specifically, rather than, generally, for all code, at once, as a whole, over time, generally, because, a pattern, that was, useful, when introduced, may have, become, unnecessary, as requirements, changed, became, sufficient, for the code's, current needs, in practice, for real users, with real requirements, that determine, is still, justified, for that code, specifically, rather than, generally, for all code, at once, as a whole, over time, generally. Suggesting, removal, of a pattern, that no longer, earns its complexity, is a valuable, review contribution, because, it simplifies, code, for future maintainers, who need not, understand, pattern machinery, that serves, no current purpose, for the program, as a whole, over its run, generally, and specifically, for each pattern, reviewed, must be, justified, by current requirements, rather than, by history, alone, for that code, specifically, rather than, generally, for all code, at once, as a whole, over time, generally. That review, discipline, of identifying, checking consistency, and questioning, continued need, for patterns, in code, under review, helps, a team, keep its designs, clean, over time, as programs, evolve, and as patterns, are introduced, adapted, and occasionally, removed, for good reasons, based on, current requirements, rather than, on habit, or on history, alone, for that code, specifically, rather than, generally, for all code, at once, as a whole, over time, generally.
Check: What three things should a reviewer check about pattern use in code under review?
Section 49
A short catalogue, of patterns, worth knowing, by name, helps, you, recognise, them, quickly, when you, encounter them, in code, with colleagues, who use, pattern names, as shorthand, generally, and specifically, should be, familiar, enough, to trigger, recognition, specifically, rather than, generally, at once, over time, generally. For creation, know, factory, for creating, objects, without coupling, callers, in detail, of object, created, over time, generally; builder, for constructing, complex objects, that guide, callers, through construction, specifically, rather than, generally, at once, as a whole, over time, generally; and singleton, for a single, shared instance, used, sparingly, because, costs, for testing, and for reasoning, about code, that uses it, for its purpose, in the program, as a whole, over its run, generally, and specifically, where singleton, is considered, as a design, for that instance, specifically, rather than, generally, for all instances, at once, as a whole, over time, generally.
For structure, know, adapter, for converting, an incompatible interface, into one, that clients expect, without modifying, either side, directly, for that interface, specifically, rather than, generally, for all interfaces, at once, as a whole, over time, generally; decorator, for adding, responsibilities, dynamically, by wrapping, an object, without subclass explosion, for combinations, of responsibilities, in practice, generally, and specifically, for each combination, in a program, where decorator, provides, flexible composition, without a subclass, for each combination, of features, over time, generally; facade, for simplifying, a complex subsystem, behind a task-based, simple interface, for most users, of that subsystem, specifically, rather than, generally, for all subsystems, at once, as a whole, over time, generally; and composite, for treating, single items, and groups, uniformly, for tree-like structures, with leaves, and groups, nested, to any depth, that the data, actually requires, in practice, generally, and specifically, in a program, where composite, provides, uniform handling, alike, generally.
For behaviour, know, observer, for notifying, many objects, when state changes, without coupling, the subject, to its observers, individually, for that subject, specifically, rather than, generally, for all subjects, at once, as a whole, over time, generally; strategy, for choosing, an algorithm, at run time, from among, several alternatives, without hard-coding, the choice, into the code, for its task, specifically, rather than, generally, for all tasks, at once, as a whole, over time, generally; command, for encapsulating, a request, as an object, that can be, stored, queued, logged, or undone, for that request, specifically, rather than, generally, for all requests, at once, as a whole, over time, generally; and state, for changing, an object's behaviour, with its state, through separate, state objects, rather than, through conditionals, scattered, through its methods, for that object, specifically, rather than, generally, for all objects, at once, as a whole, over time, generally. Knowing, these patterns, by name, and by problem, is sufficient, for most, everyday design work, in Python programs, of ordinary size, and ordinary complexity, for real users, with real requirements, that determine, which patterns, actually arise, in practice, for those programs, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and other patterns, in this chapter, can be, learned, as needed, when their problems, actually appear, in your work, with real programs, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally.
Check: Which patterns form a useful short catalogue for everyday Python design work?
Section 50
This chapter has covered design patterns in Python, through creational, structural, architectural patterns, anti-patterns, and refactoring, generally, and specifically, covered, where its problem, its structure, and its consequences, were described, for that pattern, specifically, rather than, generally, for all patterns, at once, as a whole, over time, generally. Its central lesson is that patterns are tools for recognising, and solving, recurring design problems, with proven structures, from their names, and their forms, in code, as a whole, over its run, generally, and not, goals, in themselves, to be, applied, as widely, as possible, to every program, you write, from now on, generally, regardless, of whether, those programs, actually have, the problems, that patterns, solve, for real users, with real requirements, that determine, is justified, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally.
The habit to carry forward is to ask, what problem, it actually is, in concrete terms, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, whether, a known pattern, matches, that problem, closely enough, to justify, its complexity, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally, and whether, a simpler, Pythonic solution, using, functions, or built-in structures, would serve, just as well, with far less, machinery, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally. That habit, of problem-first thinking, rather than, pattern-first thinking, prevents, most cases, of pattern overuse, where, enthusiasm, for patterns, leads, to designs, that are, more complex, and that are, harder, to maintain, than simpler designs, would have been, in practice, generally, and specifically, for each design, where a pattern, was applied, without asking, whether it, was actually needed, for that problem, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally.
Finally, keep learning patterns, gradually, as their problems, appear, in your work, with real programs, specifically, rather than, generally, for all problems, at once, as a whole, over time, generally, rather than, trying, to memorise, every pattern, in a catalogue, at once, before you, have encountered, the problems, they solve, in practice, for real programs, with real requirements, that determine, whether a pattern, matters, for your work, specifically, rather than, generally, for all work, at once, as a whole, over time, generally. A pattern, learned, through solving, a real problem, with it, with real users, who benefit, from its solution, in practice, generally, will stay, with you, far longer, than a pattern, memorised, from a catalogue, without ever, applying it, for real users, over time, generally, and specifically, for each pattern, you learn, through practice, where its problem, and its solution, become, generally, and specifically, where that pattern, may apply, with similar forces, and similar trade-offs, for that program, specifically, rather than, generally, for all programs, at once, as a whole, over time, generally. Carry, that problem-first habit, and patterns, will serve you, as useful tools, rather than, as burdens, with patterns, as part, of your practice, from now on, generally, and specifically, for each program, you will write, or maintain, with patterns, as appropriate, over its lifetime, as a whole, over time, generally.
Check: What problem-first habit summarises this chapter's approach to design patterns?
Practical Task
Pick a small program you have written, or write a short program with a design problem it currently handles awkwardly, such as creating objects of different kinds based on input, or notifying several parts when data changes. Identify which pattern from this chapter matches that problem, or decide honestly that no pattern is needed and a simpler solution suffices, and explain your reasoning in a short note. Implement the pattern, or the simpler solution, in a small, tested change, keeping behaviour unchanged for existing uses. Then review your change by asking the four questions from Section 30, and write a second short note stating whether the pattern earned its complexity, what you would change on reflection, and whether a future maintainer would understand your choice from your code and comments alone.
Chapter Questions
- Why is applying a pattern where its problem does not exist worse than using no pattern at all?
- How does a factory centralise creation logic, and when is direct construction clearer instead?
- How do decorator and adapter differ, even though both wrap an object?
- Why does the observer pattern make adding new observers easy, and what care is needed around observer lifetimes?
- How should you decide whether a pattern that was useful when introduced is still worth its complexity today?