When facing a problem, a developer will certainly draw from the well of personal experience for any solutions to similar problems that worked in the past. Such building blocks are nothing more than hints and represent the skeleton of a solution. However, these same building blocks can become more refined day after day and generalized after each usage to become applicable to a wider range of problems and scenarios. Such building blocks might not provide a direct solution, but they usually help you to find your (right) way. And using them is usually more effective and faster than starting from scratch.
These building blocks are known as patterns.
The word pattern is one of those overloaded terms that morphed from its common usage to assume a specific meaning in computer science. According to the dictionary, a pattern is a template or model that can be used to generate things—all kinds of things. In computer science, we use patterns
in design solutions at two levels: implementation and architecture the key is to focus on the problem at hand, understand the context and trade-offs, and then select the appropriate design pattern (or combination of patterns) that can help you solve the problem in a more maintainable, extensible, and flexible way.
What’s a design pattern, exactly?
We software professionals can attribute the concept of design patterns to an architect—a real architect, not a software architect. In the late 1970s, Christopher Alexander developed a pattern
language to let individuals express their innate sense of design through a sort of informal grammar. From his work, here’s the definition of a pattern:
Each pattern describes a problem that occurs over and over again in our environment, and then describes the core solution to that problem, in such a way that you can use the solution a million times over, without ever doing it the same way twice.
Nicely enough, although the definition was not written with software development in mind, it applies perfectly to that. So what’s a design pattern?
A design pattern is a known and well-established core solution applicable to a family of concrete problems that might show up during implementation. A design pattern is a core solution and, as such, it might need adaptation to a speciic context. This feature becomes a major strength when you consider that, in this way, the same pattern can be applied many times in many slightly different scenarios.
Design patterns are not created in a lab; quite the reverse. They originate from the real world and from the direct experience of developers and architects. You can think of a design pattern as a
package that includes the description of a problem, a list of actors participating in the problem, and a practical solution.
How to work with design patterns
Here is a list of what design patterns are not:
■ Design patterns are not the verb and should never be interpreted dogmatically.
■ Design patterns are not Superman and will never magically pop up to save a project in trouble.
■ Design patterns are neither the dark side nor the light side of the Force. They might be with you, but they won’t provide you with any special extra power.
Design patterns are just helpful, and that should be enough.
You don’t choose a design pattern; the most appropriate design pattern normally emerges out of your refactoring steps. We could say that the pattern is buried under your classes, but digging it out is
entirely up to you.
The wrong way to deal with design patterns is by going through a list of patterns you might ind in books and matching them to the problem. Instead, it works the other way around. You have a problem and you have to match the problem to the pattern. How can you do that? It’s quite simple to explain, but it’s not so easy to apply.
You have to understand the problem and generalize it.
If you can take the problem back to its roots and get the gist of it, you’ll probably ind a tailor-made pattern just waiting for you. Why is this so? Well, if you really reached the root of the problem, chances are that someone else did the same in the past 20 years (the period during which design patterns became more widely used). So the solution is probably just there for you to read and apply.
This observation prompts us to mention the way in which all members of our teams use books on design patterns. (By the way, there are always plenty of such books scattered throughout the ofice.)
Design patterns books are an essential tool. But we never read such books. We use them, instead, like cookbooks.
What we normally do is stop reading after the irst few pages precisely where most books list the patterns they cover in detail inside. Next, we put the book aside and possibly within reach. Whenever we encounter a problem, we try to generalize it, and then we lip through the pages of the book to ind a pattern that possibly matches it. We ind one much more often than not. And if we don’t, we repeat the process in an attempt to come to a better generalization of the problem.
When we ind the pattern, we start working on its adaptation to our context. This often requires refactoring of the code which, in turn, might lead to a more appropriate pattern. And the loop goes on.
Where’s the value in patterns, exactly?
Many people would agree in principle that there’s plenty of value in design patterns. Fewer people, though, would be able to indicate what the value is and where it can be found. Using design patterns, per se, doesn’t make your solution more valuable. What really matters, at the end of the day, is whether or not your solution works and meets requirements.
Armed with requirements and design principles, you are up to the task of solving a problem. On your way to the solution, though, a systematic application of design principles to the problem sooner
or later takes you into the immediate neighborhood of a known design pattern. That’s a certainty because, ultimately, patterns are solutions that others have already found and catalogued.
At that point, you have a solution with some structural likeness to a known design pattern. It is up to you, then, to determine whether an explicit refactoring to that pattern will bring some added value
to the solution. Basically, you have to decide whether or not the known pattern you found represents a further, and desirable, reinement of your current solution. Don’t worry if your solution doesn’t match a pattern. It means that you have a solution that works and you’re happy with that. You’re just fine. You never want to change a winning team!
In summary, patterns might be an end when you refactor according to them, and they might be a means when you face a problem that is clearly resolved by a particular pattern. Patterns are not an added value for your solution, but they are valuable for you as an architect or a developer looking for a solution.
Understand the purpose of design patterns: Before delving into specific patterns, it's crucial to grasp the fundamental reasons why design patterns exist. They are proven solutions to common software design problems, which help create more flexible, maintainable, and scalable code. They provide a shared vocabulary for communicating design decisions and promote best practices.
Cultivate a problem-driven mindset: Rather than starting with a specific design pattern in mind, focus on the problems you're trying to solve in your codebase. Observe the recurring issues, such as tight coupling, rigid structure, or difficulty handling change. This will help you identify the areas where design patterns can be most beneficial.
Develop design pattern awareness: Familiarize yourself with the most common and influential design patterns (e.g., Singleton, Factory, Observer, Decorator, Adapter, etc.). Understand their key characteristics, use cases, and the problems they address. This awareness will help you recognize patterns in your code and recognize opportunities for applying them.
Analyze your code for "code smells": Be on the lookout for code smells - indicators of potential design issues, such as large monolithic classes, excessive conditional logic, or duplicated code. These are often signs that a design pattern could improve the structure and flexibility of your codebase.
Consider the context and trade-offs: When identifying a potential design pattern application, also consider the specific context of your project, including requirements, constraints, and the team's familiarity with the pattern. Each pattern has its own set of trade-offs, such as increased complexity, performance implications, or learning curve. Evaluate whether the benefits outweigh the costs.
Start small and iterative: Don't feel the need to apply design patterns everywhere. Begin with the most pressing design issues and introduce patterns gradually. Refactor your code in small, incremental steps, validating the improvements at each stage. This approach helps you build experience and confidence in applying design patterns effectively.
Leverage design principles: In addition to design patterns, familiarize yourself with fundamental design principles, such as SOLID, KISS, DRY, and YAGNI. These principles can guide your decision-making and help you identify opportunities for applying design patterns.
Cultivate a learning mindset: Design patterns are not a one-time study; they require continuous learning and adaptation. Stay up-to-date with the latest developments, attend workshops, and engage in discussions with experienced developers. This will help you refine your understanding and expand your design pattern toolkit.
The key is to maintain a balanced approach, where design patterns complement your overall software design process, rather than becoming an end in themselves. By focusing on problem-solving, code analysis, and incremental improvement, you'll develop a robust intuition for when and how to apply design patterns effectively.
Let's dive into some common problems that can be addressed by different categories of design patterns.
Creational Patterns:
Excessive object creation and configuration: You may have a class that requires a lot of parameters to construct, leading to complex and error-prone object instantiation. This is a good candidate for a Factory Pattern or an Abstract Factory Pattern.
Global access to object instances: If you need to ensure that only one instance of a class exists throughout your application, the Singleton Pattern can help you manage the lifecycle of that instance.
Flexibility in object creation: When you need to create different types of objects based on certain conditions, the Builder Pattern can simplify the object creation process and make it more extensible.
Behavioral Patterns:
Tight coupling between classes: If your classes are tightly coupled, making it difficult to change or extend the system, the Observer Pattern can help decouple the relationship between the subject and its observers.
Complex control flow and decision-making: If your code has a lot of conditional logic or complex algorithms, the Strategy Pattern can help you encapsulate different algorithms and make the code more modular and testable.
Difficulty in tracking and coordinating the execution of multiple objects: When you have a set of objects that need to coordinate their actions, the Command Pattern can help you encapsulate requests as objects, allowing for more flexible and extensible behavior.
Structural Patterns:
Rigid or inflexible class hierarchies: If your class hierarchy is too rigid or difficult to extend, the Adapter Pattern can help you adapt the interface of a class to match the expected interface of another class.
Duplicate or similar code: If you have similar code scattered across multiple classes, the Decorator Pattern can help you add additional responsibilities to objects dynamically without affecting the code of the underlying objects.
Complex or hierarchical object structures: When you have a complex object structure that needs to be traversed or manipulated, the Composite Pattern can help you treat individual objects and compositions of objects uniformly.
These are just a few examples of the problems that different categories of design patterns can help solve. As you encounter these types of issues in your codebase, start thinking about how the various design patterns could be applied to address them. This problem-driven mindset will help you develop a better intuition for when and where to apply design patterns effectively.
Top comments (0)