Leveraging Design Patterns for Better AI Code.
It’s incredible how the Gang of Four (GoF) book, Design Patterns: Elements of Reusable Object-Oriented Software. Fits really well in the majority of development challenges. The idea to have a catalog of design implementation for each kind of problem is brilliant.
This approach facilitates many points when building a solution, making it easier to analyze trade-offs between patterns, and most importantly, facilitates the communication between the team members.
The GoF book was only the beginning, and nowadays we have patterns basically for everything, from how to use a simple function to integrating systems until we build whole infrastructures. When you bring a pattern to the discussion, you get a huge shortcut to discuss the trade-off about that pattern, removing the noise of how to explain the implementation itself and reducing the risk of bad interpretation that carries time waste for small details.
If patterns have always facilitated human communication, how do they serve us now that our pair programmer is an inherently non-deterministic large language model?
Now, in the AI era, the pattern has gained more importance than ever. The true challenge of generative AI is not its capability but its reliance on Natural Language Processing (NLP), which forces us to confront the inherent ambiguity and non-determinism of human language. As we communicate through NLP, all fallacies of the human language are automatically inherited by the AI.
A single word can have different meanings depending on the context and how it’s used, opening a brief gap for misinterpretation or, in the AI cases, hallucinations. And instead of wasting some minutes in a call to explain the concepts and how to implement them, AI agents can be dangerous by committing something totally different from what was planned.
Let’s test in practice a simple example to generate a function that receives a value and a code, where, depending on the code, some different logic is applied about how to increase the value.
Prompt:
Output:
The code is done and does what it proposes, but we know that looking with more advanced eyes, we can encounter many problems in this implementation. The main issue is that it violates the Open/Closed Principle (OCP), requiring the core function to be modified every time a new business rule is added. This prompt takes more or less 142 tokens, and we definitely can improve by adding all these constraints and best practices to have a better result.
But, analyzing this problem itself, we can use a strategy pattern, where we can have the same assertions but with some extra powers. Let’s see:
Prompt:
Output:
The goal here isn't to show code or prompt techniques but to make clear that the simple act of specifying a pattern can reduce the prompt itself and be firmly grounded in the need to manage non-deterministic results.
We have reduced it to 123 tokens, and even though we have many problems in this implementation as well, the output clearly has improved. Now we have a separate object that allows us to add more flows with more complex logics without worrying about updating our main function. Achieving the OCP and modularity by separating the "what" (business rule) from the "how" (implementation logic).
For absolute OCP, we can use other patterns, like the factory, or include other mechanisms to dynamically register strategies from other files or modules, like dependency injection. Allowing new logic to be added without ever touching the “calculateStrategy” function or its strategy map. Providing a clear contrast between improved modularity and full pattern implementation.
Even in this simple example, it’s possible to see many advantages in use patterns inside the interactions with AI. We gain:
Less token usage: In big contexts and more complex requirements. Simply telling the pattern that needs to be used instead of writing the explanation can reduce a lot of the token usage.
Output integrity and constraint: Patterns serve as a precise, high-level vocabulary that drastically constrains the LLM's output space, thus mitigating the risk of 'hallucination' and ensuring the code structure meets professional standards.
Better output: By simply being more technical, the output gets improved, and other best practices are implemented also.
Better conventions: As the codebase grows, the convention applied to the code itself is used as context to improve and keep consistency in each new interaction.
Patterns are definitely a hidden gem in the software engineering industry. This capacity to share the same language to solve a big variety of challenges, in fact, simplifies and allows us to scale our decisions in a direct way. Facilitating communications, documentation, and trade-off analysis also dramatically reduces the learning curve. With AI, help us guarantee integrity in the outputs and reduce the token usage and chances of hallucinations.
AI accelerates creation, but the engineer's role is ensuring maintenance and integrity. A pattern-driven prompt is an act of proactive refactoring before the code is even written, optimizing for maintainability, not just creation.