Skip to content

Classic Design Patterns in Python

Based on a talk by Brandon Rhodes: https://rhodesmill.org/brandon/talks/#where-are-they-now

See also GoF Pattern in Python


Executive Summary

The core themes driving the evaluation are:
1. Python’s Language Features: Python’s dynamic nature and first-class functions render many classic patterns overly complex or obsolete.
2. Love Your Data: Patterns that obscure data, hide its format, or create complex, untraceable state changes are now considered anti-patterns. Modern development favors explicit, inspectable data.
3. Managing Complexity: The surviving patterns are those that solve fundamental problems of complexity, either by composing behavior, structuring frameworks, or adapting interfaces.

The “Good” and Surviving Patterns

These patterns remain relevant because they address fundamental aspects of software design and complexity that persist regardless of language features.

1. The “Framework” Patterns

These are foundational patterns that you are more likely to use as part of a larger library (like a browser DOM or a web framework) than to implement from scratch.

  • Composite: The bedrock of any tree-like structure. It allows you to treat both individual objects (leaves) and groups of objects (branches) uniformly. The browser DOM is the quintessential example.
  • Chain of Responsibility: Works hand-in-hand with the Composite pattern. It defines a clean way for an event to be passed up a hierarchy until it’s handled. This is exactly how event “bubbling” works in web browsers.
  • Command: Essential for any system that requires undo/redo functionality. By encapsulating an action and its inverse in an object, you can create a history of operations. It is also powerful for managing transactional workflows, such as database migrations in Django.
  • Interpreter: The ideal solution for implementing a small, domain-specific language (DSL). It uses a composite structure to represent an expression or program as an executable syntax tree.

2. The “Decomposition” Patterns

These patterns are valuable tools for breaking apart large, monolithic classes and separating concerns, fully embracing the principle of “favor composition over inheritance.”

  • Bridge: Decouples an abstraction from its implementation, allowing both to vary independently. It’s the solution for avoiding a combinatorial explosion of subclasses.
  • Decorator: Dynamically adds behavior to an object without altering its class. While Python has language-level decorators (@), the original object-wrapping pattern is still very useful for applying layers of functionality at runtime.
  • Mediator: Centralizes complex interactions between a set of objects. Instead of creating a tangled web where everyone talks to everyone, objects only talk to the mediator, simplifying the logic.
  • State: A clean way to manage an object whose behavior changes dramatically based on its state. It avoids complex conditional logic by encapsulating state-specific behaviors in distinct state objects.

3. Small but Useful Patterns

These are practical, widely applicable patterns for everyday coding challenges.

  • Builder: Excellent for constructing complex objects step-by-step. It provides a fluent API that hides the messy details of the object’s internal construction. The matplotlib library’s API for building plots is a great example.
  • Adapter: Makes an existing class’s interface conform to the one a client expects. It’s a fundamental pattern for integrating incompatible libraries or legacy code.
  • Flyweight: A memory optimization technique for sharing the immutable parts of an object’s state among many instances. Python’s pre-allocation of small integer objects is a language-level application of this principle.

The “Bad” or Less Relevant Patterns

These patterns are considered “bad” not because their original goal was wrong, but because modern languages and architectural principles offer far better solutions.

1. Patterns Made Obsolete by Python’s Features

These patterns were primarily workarounds for the limitations of older, statically-typed languages like C++ and Smalltalk. Python’s core features provide a more direct and simpler solution.

  • Factory Method & Template Method: These rely on subclassing to change a small piece of behavior. In Python, it’s far simpler to just pass a function or a class directly as an argument.
  • Abstract Factory: While better than Factory Method because it uses composition, it’s still unnecessarily verbose. Instead of creating a whole factory object to pass around, you can just pass the necessary functions or constructors.
  • Strategy: The classic use case is swapping algorithms. In Python, you don’t need to wrap each algorithm in a separate class; you just pass the desired function.
  • Singleton: The complex implementation described by the GoF to enforce a single instance is unnecessary. In Python, you can achieve the same result by simply instantiating a class once at the module level and importing that instance.

2. Anti-Patterns that Obscure Data

These patterns violate the modern principle that data should be transparent, inspectable, and easy to reason about.

  • Proxy (specifically, the Remote Proxy): This pattern makes a remote network call look like a local method call, hiding the inherent latency, failure modes, and different design considerations of network communication. Modern approaches like explicit REST APIs or gRPC are preferred because they make the network boundary clear.
  • Memento: This pattern saves an object’s state as an opaque binary blob. This is a nightmare for debugging, data migration, and interoperability. The modern approach is to use standard, transparent serialization formats like JSON, YAML, or Protocol Buffers.
  • Observer: This pattern leads to a complex web of objects directly updating each other, making the flow of data hard to trace (which Rhodes calls “Domino Programming”). The modern alternative, exemplified by React’s architecture, is to have a single, inert, centralized data structure as the “source of truth.” Components react to changes in this central state, creating a unidirectional and much more debuggable data flow.

Full transcript

From: https://www.youtube.com/watch?v=pGq7Cr2ekVM

About the “Design Patterns” Book

Design patterns was a book that came out back in the 90s. It was a long time ago, but I still see questions about it online, and so I thought it would be interesting to review whether any of the book is still relevant today. It came out in ‘94. It was a book on a fad at the time called object-oriented programming. It was by four people that no one could remember the names of, so they just called them the “Gang of Four” because there weren’t any other programming books written by four people.

It described 23 design patterns, they called them, that had become common solutions for the problems with these newfangled object-oriented languages.

Goal of the Talk

My goal for this talk is to make a brief survey of the 23 patterns, asking these questions: can we learn anything from the ones that survived? Can we learn any things from the ones that did not?

There will be limitations to this talk. I’m going to have to cover 23 design patterns in under an hour. There will not therefore be much time for nuance or corner cases. It reflects my experience programming a lot of Python, less C and C++. Not your experiences, which might be different. And it will not be entirely fair towards the visitor pattern, for which I apologize in advance.

The goal is for these old patterns to help us reflect on how we program today. Happily, the design patterns book is online, so you can review the patterns yourself later to develop your own opinions if you don’t like mine. If you look up “design patterns gang of four,” this unc.edu site from a CS Professor should be high in the results and has the full text of the original copy of the book with all the code examples, mostly in a small talk in C++.

Some of my own ideas on design patterns as they apply to Python are online if you want to check those afterwards as well. So are you ready? Here we go. 23 patterns that they grouped into three. And what should we go with first? The ones that maybe survived or the ones that did not?

Obsolete Patterns (Due to Language Evolution)

First, let’s cross off the patterns that didn’t really wind up being relevant if your programming language offers first-class functions. What are first-class functions, I hear you cry? Those are functions that can be passed as an argument to another procedure or routine and placed in a data structure and associated with data. You might think, “what language doesn’t allow that?” Smalltalk, the language available in ‘94, the pioneer language of object orientation, didn’t let you pass functions or procedures. So there were a number of design patterns that were just kind of trying to get around this problem with Smalltalk and some of its descendant languages. Let’s look at them quickly.

The Factory Method said, “all right, well, what if we need to give a piece of code to a big class like ‘Application’?” Big classes like that were popular back then because just to bring up a window on the screen could take 100 lines of code in ‘94 on Windows or on a Mac. You want to wrap that up in an object. So the idea was, “oh, well, if once the application object is up and running, if it needs to know how to create your document, it doesn’t know whether you’re programming a spreadsheet or a word processor. It’s going to need to know how to call a ‘create document’ procedure that builds and returns an instance of your document class.”

Well, how could it do that if we can’t pass functions? So their idea was that you would subclass ‘Application’. Yes, make your entire own subclass of ‘Application’ just to add one line of code. Not only does that sound hopelessly awkward today, even “Design Patterns” itself back in 1994 offered a better approach two chapters earlier. They had already described the Abstract Factory.

The Abstract Factory says, “let’s not make a subclass of ‘Application’. Let’s instead design ‘Application’ so that it takes as a constructor argument a little bitty class that has your special line of code in it that knows how to build your particular subclass of ‘Document’.”

Why should we prefer that second pattern, the Abstract Factory, over the first one, the Factory Method? Because of a fundamental design principle from the introduction chapter of the Design Patterns book itself. In fact, it was so important that they didn’t bury it in a paragraph; they put it on a line by itself, just like this, in italics:

Favor object composition over class inheritance.

One of the fundamental principles of the book. What is composition? You have, in their terms, “composed” two objects when you give one of them a reference to the other. So if I create ‘my document factory’ and then pass it as a constructor argument to the ‘Application’ that it saves as an instance variable, I have composed those two objects.

Favor object composition over class inheritance. Why? Go read the introduction to read the full slate of reasons, but a quick one I’ll mention is because putting objects together dynamically at runtime is simply more flexible than writing a bunch of extra classes at compile time. And so they always favor object composition.

Factory method? Well, it’s class inheritance. The Abstract Factory, as we saw a minute ago, does the same thing with object composition, and so it is the better pattern by their own rules. So you might ask, “well then why did Design Patterns include the Factory Method? Why write an entire chapter on it if a better alternative already existed?” And this is something important to understand. Because they wanted the book to be a complete catalog of all common object-oriented practices. They did not limit themselves to best practices. They wanted you to be able to find all the patterns you were likely to encounter in 1994 programming. And the Factory Method, despite its awkwardness, was in widespread use. In their own words, “Factory methods pervade toolkits and frameworks,” and they were able offhand to give a long list of frameworks that were always having you subclass in order to specialize.

So they had two different ways to parameterize object creation, but neither one is a pattern that we tend to use in modern languages. I go years at a time without seeing either of those patterns because our languages today have first-class functions. And in that case, you don’t need to pass ‘Application’ an instance of a class that you’ve attached a method to. You just pass in your constructor function. Some languages even have first-class types, and you can just pass the class itself to be instantiated. And so those two patterns have largely fallen out of use among people that use modern, especially dynamic languages.

Alright, what about some of these other patterns? Let’s go ahead down in the lower right and look at Template Method. Template Method should look familiar. It should look like one of the two patterns we just looked at. This asks, what if ‘Application’ needs you to give it three procedures, not one? Well, then you should have to write a subclass and override the three abstract methods with three concrete procedures. By the logic of the book’s own introduction, that we should favor object composition over class inheritance, I think it’s clear that rather than inheriting a huge object that you don’t understand the internals of and trying to write three new methods for it, it would be much clearer if you just had a separate type that you subclassed and built off of to provide the three methods that this fictional application needs you as the programmer to provide to it. So by the book’s own logic, let’s also go ahead and cross off Template Method.

Alright, up to the left: Prototype pattern. That was actually kind of interesting. Imagine, they say in their chapter, that we’re writing a music composition application that wants to let users create four kinds of objects. Here they are. Notice, and this is the challenge, they’re of different classes, different subclasses of some parent class, and some of their constructors need arguments and some don’t. So it’s kind of a mixed bag, the rules by which you create these. Now, how could you explain to, say, a menu widget that’s going to be on the screen how to create these objects when your menu items are selected, when some of them have different classes and some of them have different constructor arguments?

Now, in a modern language, of course, you’re already thinking you could just pass a data structure. The people who designed the menu system probably just want a plain data structure with the names of some menu items, the callable (the function, the type) that needs to be invoked, and then any arguments that they need.

Alright, but the Gang of Four in 1994 have to ask, what if your language isn’t that powerful? Are you going to need as many factories as kinds of objects that you want to create? Not just one per class, but even one per individual kind of note? The Prototype pattern said, “Ah, no. Simply create examples of the four objects and give them each a ‘clone’ method.” The idea here is that if you give a collection of objects a ‘clone’ method, where what happens when you call ‘clone’? Well, it builds and returns to you a new instance using the same constructor arguments as were used to construct the object you’re calling. Then, if I have three kinds of note, I just instantiate them, I pass them to the framework, and it calls ‘clone’ to get new copies of notes with the same constructor argument. You’ve replaced needing to have a whole slate of separate constructors with just needing a ‘clone’ method on each class. Thanks to the Prototype pattern’s ‘clone’ method, we can give plain object instances to the menu instead of separate factories. It’s an interesting solution to a problem. I don’t actually dislike it; I’ve just never seen it before. I have never seen that problem come up in modern languages because we just don’t get into that situation. Though I have to admit, I like the solution and find it elegant at a level that I don’t for these first few patterns that we crossed off.

Alright, the next one’s the Singleton. I’m not going to spend much time on this. Singletons are those annoying global objects that code seems to need access to but you don’t want to pass everywhere. Do we need singletons? Sometimes, maybe. That’s a big topic of its own, and people give entire talks about singletons. But do we need the Singleton pattern? If you go look at their chapter, they have this whole elaborate story about how you can contort the class itself to force it to be used as a singleton. It refuses to build multiple copies of the class so that it can only be used as a singleton.

Do we need that pattern even if we need singletons? In my experience, no. In fact, I’ve been in million-line codebases before in some of my work and never seen the Singleton pattern. Just write the class normally, build a single instance, and provide a global name or a global function that returns that instance. From the Python standard library, you call logging.getLogger(), and everyone who calls that gets the same object back, so you’re all logging to the same file. It works fine without the pattern. Why avoid the Singleton pattern? Again, it’s a whole technical discussion of its own, but one hint of why that’s easy to explain is that testing becomes hard, and tests of the Singleton become coupled if you really truly can’t create a fresh instance, so that all of your tests are having to share. That’s just one reason not to follow their contorted pattern for delivering singletons. And so I don’t encounter it in practice, and so we cross it out. Ah, we’re making progress.

Alright, one last one that tends to disappear because of the power of modern languages is the Strategy pattern. The Strategy pattern’s example is, “what if you had an object that needed to be given a specific paragraph-breaking routine at runtime out of three that are available at runtime?” You can’t specify it when it compiles because it might be a config file or something that selects it. The Strategy pattern says, “Well, put the three versions of the routine in three single-method classes, build an instance of each, and pass them as constructor arguments at runtime to the routine that needs them.”

We today can solve this more simply in any modern language. You solve this problem by just writing three functions and passing one in as an argument. Done. Once again, we don’t have to wrap up a procedure as a method of a class of which we build an instance. We just pass procedures around when we need them. So in my experience, the Strategy pattern never comes up in modern languages, and so for my purposes, I cross it off.

Alright, in powerful modern languages, these six patterns disappear because they aren’t needed. So we’re down to 17. Making some progress.

Patterns Integrated into Languages

What should we tackle next? Well, since we’re on the topic of programming languages, there are two patterns that are also disappearing from our code, but it’s because they’re so useful, they’re now getting built into our languages. These are the Iterator and the Visitor pattern.

We’ll describe them briefly. Both of them involve a producer object, which is going to build or retrieve items, and a consumer object that wants to iterate over those items without needing to know the details of how they’re being produced or stored. The patterns are opposites of each other.

The Iterator pattern says, “Well, let’s write the producer to offer a callback method.” Very often it has a name like ‘next’. The consumer then loops, calling ‘next’ over and over until the producer is done delivering objects.

The Visitor pattern is the opposite. The producer is in control, iterating across a tree or other data structure and calling the consumer, which offers one or more callback methods that are used to deliver the next item.

In legacy languages, you have to choose one approach or the other. Either way, someone’s class has to suffer from callback-style programming. The solution to this, which was actually invented in the ’70s, is called generators. They were in a pair of obscure languages and were revived in 2001 when Python added them. They were so popular they’ve now been added to C#, JavaScript, and Ruby.

What is this idea, and why is it taking the world by storm? Well, before generators, you had to write up the Iterator pattern yourself. You had to make a little class with a ‘next’ method. After generators have been added to your language, you get a new control flow keyword called ‘yield’. You now just write a normal loop and ‘yield’ each time you have a new item that’s ready for delivery to the consumer. Each time the consumer asks for an item, the producer runs enough code to reach its next ‘yield’ and deliver the item. So the consumer’s requests for more items gradually cause the producer, one step at a time, to run all the way to completion. So both the producer and consumer can be written with normal control flow statements like ‘if’, ‘for’, and ‘while’. Neither is forced to define methods or endure callbacks.

So, Iterator and Visitor, great patterns, but I don’t write ‘next’ methods anymore because the languages I use have now built both of those patterns into the language. So they’re still there, they’re powerful, and they’re working, but as design patterns, they disappear from my code because I no longer have to implement them. So for the purposes of this talk, I’m crossing them off the list.

Anti-Patterns (Data Obscurity)

Alright, only 15 left. Are there any more that we can eliminate? I think there are three: Proxy, Memento, and Observer. What are these?

Proxy actually has several uses, but I will limit myself to the ‘remote proxy’, which was the most famous and widespread use of the pattern, where the methods of a class send requests across the network. It looks like you have a user class right in memory, but in fact, every method call is actually going to another server to get the return value.

Second, the Memento. Memento is an object state saved as a binary string. So, your big application is about to exit, but you want to be able to rebuild its state when it comes back up. The Memento pattern says, “Well, teach the object to write itself out as bytes, and then when you run the application again, you can read the bytes back in and the object will be recreated.”

The Observer pattern, finally, is where an object can offer subscriptions to its changes, and other objects called subscribers can receive callbacks when the data in the object changes.

So why am I putting these three patterns together? I will explain it by explaining what are their sins. What did they each do wrong? The Proxy pattern hides how data’s passed across the network. The Memento pattern hides how data is persisted to disk; it’s just a binary blob that you don’t know how to introspect. And the Observer pattern scatters data across a bunch of objects that communicate about changes rather than unifying it.

In other words, all three patterns do something wrong with data. And you should love your data. You want to see it, you want to be in control of it, you don’t want it to disappear and be an obscure private format that you don’t understand.

In the jobs I’ve had over the last 10 or 15 years, I don’t see the Proxy pattern. Network APIs between services should use a data format you understand and can document. Good object methods for things in memory tend to be small, orthogonal, and numerous. Good remote procedures tend to be comprehensive, aggregate, and few.

Second, the Memento pattern. Data should be persisted in a format that’s independent of your programming language and that you can access directly using other tools.

Finally, the Observer pattern. Should we really be building forests of objects that are subscribed to each other’s state changes? Turns out the answer is no. Go look on YouTube for any recent Facebook talk about their React library and about how hard it was to make the Facebook page stay consistent. React today is the most popular JS library. Why is it so popular? Data. It says that instead of your data living on a bunch of live objects trying to send each other updates, your data should be simple and inert. Clicks, keystrokes, and server events have to write to that inert data structure. Then React diffs the object and updates the screen for you. React makes a single data structure the foundation of your application.

The Proxy, the Memento, and the Observer all three of these patterns fail to put data first. And I think that is why in the last decade of programming, I cannot remember seeing a single one of those patterns once. So we get to cross three more of them off.

Still Relevant Patterns (Decomposition)

All right, we’ve essentially eliminated half. We’ve knocked out 11 of them, leaving a dozen left. And at this point, I think I can’t go farther. I’ve run out of patterns that I can easily knock down as being irrelevant or anti-patterns. So let’s get the ones that have survived and let’s look at them in groups.

First, we will look at these four because they all have something in common: Bridge, Decorator, Mediator, and State. They are each fairly complicated, and alas, I can only summarize them very quickly.

The Bridge is the idea that one real-world object, like a window on the screen, might be implemented best by two classes. You want to specialize the window according to whether it’s a pop-up or shows icons, but you also need to subclass it for different operating systems. This requires you to subclass in two directions at once. The Bridge pattern suggests splitting the window into two objects: a high-level one that knows its behavior and a low-level implementation object that knows how to talk to the OS.

Second, Decorator. The idea here was to write a wrapper class with the same interface as the object wrapped. For example, to add scroll bars to a text view without complicating the text view class itself, you can create a ‘scroll decorator’. When it’s asked to draw, it asks the text view to draw, gets the resulting bitmap, cuts out a portion, and draws scroll bars around it. You can stack these decorators endlessly.

Third, the Mediator. If you have a list box and an entry field in a dialog, and editing one changes the options in the other, you shouldn’t put that code in either widget. Instead, you split that logic out into a separate procedure, a ‘mediator’, that gets called when either widget is edited and gets everything straight again.

Finally, the State pattern. This came from implementing something like a TCP connection, where every method had to start with an if statement checking if the connection was open or closed. The State pattern replaces these if/else stacks by having separate “state” objects in the background. The main TCP connection object swaps out a back-end state object (e.g., ‘TCP Open State’, ‘TCP Closed State’) depending on its current state.

What do these four patterns have in common? They all break the old object-oriented assumption that one real-world object should become one object in your code. They are all about splitting behaviors out into several classes. That’s an interesting enough move in the world of object orientation that I’m going to keep them, even though I have rarely used them.

Framework Patterns

All right, now we’re going to look at the really big patterns, the ones that have wound up governing the way our main applications operate today. I call these the “framework patterns” because I tend to use them, not write them. They are Composite, Chain of Responsibility, Command, and Interpreter.

The Composite pattern says to give all nodes in a hierarchy the same interface. For example, in a drawing application, make the aggregate object (the ‘picture’) have the same interface as the individual lines and rectangles. That way, pictures can hold other pictures. The DOM (Document Object Model) that underlies all web programming uses the Composite pattern. A div can hold children, an image cannot, yet they both have the same object interface, including a .children list (which is always empty for an image).

Next, the Chain of Responsibility. In a composite UI hierarchy, you pass unprocessed clicks and keystrokes up to your parent. This is universal and is called “bubbling” in the browser. You click on a piece of text inside a button; the text doesn’t care, but the event bubbles up to the button, which now knows it’s been pressed.

The Command pattern says to represent user actions as a list of objects that have both ‘do’ and ‘undo’ methods. When a user hits the letter ‘A’, you instantiate a ‘Typed A’ object and call its ‘do’ method. When the user hits Ctrl+Z, you can call the ‘undo’ method on that action object and step backwards. This is universal in browsers and GUI frameworks. It’s also used in other contexts, like the Django migration system, which saves database migrations as command objects so you can apply and undo them.

Finally, the Interpreter. It uses the Composite pattern to represent an abstract syntax tree and make it executable by giving each node a method. If a user types ‘n + 1’ in a spreadsheet, you parse it into an abstract syntax tree. Instead of compiling it to bytecode, the Interpreter pattern says to just give each node an eval method. This makes abstract syntax trees directly executable.

These four patterns have become absolutely huge and have really moved to the center of how user interfaces are implemented today.

The Facade Pattern

One remains: Facade. Facade says to hide a complex system behind a single API class. The problem is, it actually doesn’t wind up working in practice. To qualify as a Facade pattern, an API must live on only one object. But real-world APIs almost always use several. For example, jQuery has the $ function, but the rest of the API lives on the query objects you get back. It’s a collection of objects. The popular pandas library has a DataFrame object, but when you access columns, it returns smaller Series objects. So, it’s not strictly speaking a Facade. Real-world APIs are usually happy to let you access lower-level details when needed, whereas the Facade pattern often implies hiding them completely. So the Facade winds up in a kind of a twilight zone where it sounded like a great idea but just rarely works out.

Conclusion

So we’ve made it. We’ve completed our survey.
- Six patterns are useless if you have first-class functions.
- Two have become language built-ins.
- Three are real anti-patterns that obscure our data.

But half of them, I would say, survived:
- Four were rare but useful for decomposing big classes.
- Three are small but useful.
- Four are huge and dominate our frameworks.
- And finally, one final pattern was ambitious but fatally limited.

That’s my tally. You might have your own.

Page last modified: 2025-11-26 13:50:23