Design Patterns: Learning Path
By Ramas Grikštas, Back-End Engineer in the Core Services team at Kilo Health
Design patterns are typical solutions to the most common problems in software design. They help you tackle problems faster and allow you to communicate with other developers more easily. Plenty of us already use this approach, and it’s about time you started, too.
So, how and when should you use design patterns? And even more important — how can you learn them quicker?
What are design patterns?
Refactoring Guru has concisely summarized what exactly design patterns are:
“Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint that you can customize to solve a particular design problem in your code.”
Imagine you need to build different objects by variable value.
We can write “if, else” statements and solve them one by one, but this approach that is commonly used in procedural programming wouldn’t be the most effective in Object Oriented Programming (OOP). This is not how someone using OOP would solve the problem at hand.
But before we explore how to use design patterns, let’s check out the benefits they have.
Design patterns have plenty of benefits because they help to:
- Create reusable, flexible, and easier scalable code.
- Reduce your code coupling to a minimum.
- Make communication between designers more efficient — software professionals can immediately picture the high-level design when they refer to the name of the pattern used to solve a particular issue when discussing system design.
- Provide you with a way to easily solve issues related to software development using a proven solution.
- Isolate the variability that may exist in the system requirements, making the overall system easier to understand and maintain later.
Design pattern in action
Let’s get back to the issue at hand. You need to build different objects by variable value. What we need to do right now is to try to understand the problem and start to search for the solution using design patterns.
- Identify the problem
First, create different objects that implement the same interface by variable value. For instance:
- $reader = “csv”; -> build: CsvReader class
- $reader = “json”; -> build: JsonReader class
- Find the solution
To find the solution, you will need to use the resources available to you online. The best places I found to read about existing design patterns are:
- Design patterns listed on the Refactoring page, and
- DesignPatternsPHP overview, which would be especially useful if you’re searching for patterns with simple examples in PHP language.
For this problem, you don’t need to reinvent the bicycle — Object Oriented Programming has an answer to this problem. I also guarantee that design patterns will have an answer to 90% of the problems you’ve faced before.
So, in this case, the correct answer is to use a Factory design pattern. It is a creational design pattern that determines how to create an object’s mechanisms.
So, now you have defined a clear problem, and you have a solution for it. What’s left is to customize and implement it into your code.
If you don’t understand how this pattern works, don’t worry. Try to read about it a few times, but instead of trying to copy examples, figure out how it really works. Next time you face a similar problem, you will remember this pattern and find a solution faster.
Once you learn these patterns, you can use them in any programming language such as PHP, JAVA, PYTHON, C#, RUBY, GOLANG, etc.
It might take a slightly different approach to adapt them to different programming languages, but you can always find examples online on how to do that correctly.
Bad example:
<?phpdeclare(strict_types=1);// Bad approach - procedural programming way
class Reader
{
public function read(string $fileExt, string $path): array
{
if ($fileExt === "csv") {
// Csv reading logic
return [];
} if ($fileExt === "json") {
// Json reading logic
return [];
} throw new Exception("Reader not found.");
}
}$fileContentFromCsvFile = (new Reader())->read('csv', 'file_path');
$fileContentFromJsonFile = (new Reader())->read('json', 'file_path');
Good example:
<?phpdeclare(strict_types=1);// Good approach - factory design pattern
interface ReaderInterface
{
public function read(string $path): array;
public function isEligible(string $extension): bool;
}abstract class Reader implements ReaderInterface
{
public function isEligible(string $extension): bool
{
return $extension === $this->getName();
} abstract public function getName(): string;
}class CsvReader extends Reader
{
private const NAME = 'csv';
public function read(string $path): array {} public function getName(): string
{
return self::NAME;
}
}class JsonReader extends Reader
{
private const NAME = 'json';
public function read(string $path): array {} public function getName(): string
{
return self::NAME;
}
}class ReaderFactory
{
public function __construct(private Collection $readersCollection) {} public function build(string $extension): ReaderInterface
{
foreach ($this->readersCollection as $reader) {
if ($reader->isEligible($extension)) {
return $reader;
}
} throw new \LogicException('Reader not found!');
}
}// Usage: you can bind collection through service providers
$readersCollection = new Collection([
new CsvReader,
new JsonReader,
]);
$fileContent = (new ReaderFactory($readersCollection))->build('csv')->read('file_path');
How to learn all design patterns?
There are three main types of design patterns you will encounter in your work as a software engineer. These are:
- Creational patterns — This type of pattern deals with object creation mechanisms, which increase flexibility and let you reuse your existing code.
- Structural patterns — This type of pattern eases the design by identifying a simple way to realize relationships between entities.
- Behavioral patterns — This type of pattern identifies common communication patterns between objects and realizes these patterns. By doing so, these patterns increase more flexibility in carrying out this communication.
Yes, I know that it might seem like a lot, but I’m glad to announce that you don’t need to try to memorize them all at once. They will come to you with your work experience and how much time you spent analyzing them. Also, I can guarantee that not all patterns will be needed for your projects.
When I started to learn these patterns, they looked quite hard. But once you deep-dive into the problem and find a solution, the only thing you need is to just try to figure out how this pattern works and how it solves your problem.
If you manage to implement it to your code once, next time, you will remember how this pattern solves this exact problem.
The second thing is to try and learn only the most common design patterns. Leave the rest for later.
How to learn these patterns faster?
- Read programming books about design patterns and about clean code.
- Don’t judge yourself if you don’t understand patterns from the first try.
- Read different sources of information about patterns and see different solutions and approaches.
- Don’t try to learn all the patterns at once.
- Focus on one pattern at a time.
- Try to identify patterns in other people’s code.
Most important of all — try not only to read about the patterns but also to apply them to practice. Create your own project and situation where you would need to use this pattern. It will be like a small practice before really using it.
Cheatsheet of design patterns
Let’s make life easier for you. I’ve compiled a design patterns list, which I use very often. These patterns are the most common in the PHP programming language, and I think they will help you on your learning journey.
As you already know, there are 3 main types of patterns: creational, structural, and behavioral. Each of them holds several key design patterns you might find useful.
Creational patterns:
- Factory — helps you build different objects.
- Builder — helps you build complex objects in a simple way.
- Singleton — helps you have one class instance in your program.
Structural patterns:
- Dependency injection — helps you implement a loosely coupled architecture to get better (testable, maintainable, and extendable) code.
- Adapter — helps you translate one interface into another compatible interface.
- Bridge — helps you decouple an abstraction from its implementation.
- Decorator — helps you decorate or extend objects.
- Facade — helps you decouple a client and a sub-system by embedding many (but sometimes just one) interfaces and, of course, reducing complexity.
Behavioral patterns:
- Iterator — helps you make an object iterable and make it appear like a collection of objects.
- Observer — helps you notify other objects about events.
- Strategy — helps you easily switch between different strategies.
- Null object — helps you reduce the chance of null pointer exceptions. (NullObject is not a GoF design pattern but a schema that appears frequently enough to be considered a pattern).
More:
- Repository — helps you mediate between the domain and data mapping layers using a collection-like interface for accessing domain objects.
The risk of overengineering
So, we know that design patterns bring us a lot of benefits. But they also bring more complexity. When you have learned a new design pattern, don’t run to implement this in all places because not every place needs to use them.
Try to always find at least one reason why this pattern will help you scale your code in the future. If you found at least one reason, go and implement it.
Sometimes when you don’t see that in the future, you don’t need to touch your code anymore — for instance, to implement a new feature related to this code. In that case, there is no reason to implement a design pattern because it won’t bring you any benefits.
There is no point in implementing a design pattern unless you know that this pattern will give a good performance to your code later on.
Conclusion
Design patterns aren’t hard to learn, but like with everything, it takes time and energy.
Actually, it’s harder not to learn them but to apply them in everyday life. Don’t push yourself if you don’t understand them from the first time you touch them.
The understanding will come with time. The only suggestion here is not to apply them to your real projects first — instead, create a test project where you can test your knowledge.
Read more books, watch videos about clean code, and later, you will have a deeper understanding of how to write more scalable, reusable, and flexible code.
Here are a couple of recommendations I think you’ll find valuable:
- Head First Design Patterns: A Brain-Friendly Guide
- Matt Zandstra — PHP 8 Objects, Patterns, and Practice
What books would you recommend for someone who is just starting out?