It’s a common rule in software engineering that you don’t need to reinvent the wheel — use design patterns. Software design patterns are reusable solutions to general problems that you can adjust according to your needs. Of course, it’s not a one-size-fits-all solution. Sometimes you need to combine several patterns or use nothing at all. That’s why it’s important to know your product well to decide the best way to solve the problems.
Here are some of the design patterns you may find useful.
Creational patterns provide object instantiation mechanisms. They increase flexibility since they allow decoupling.
Sometimes, there are classes that should only be instantiated once. To make sure that one and only one object is created, singleton is your best friend. The idea behind it is to declare a public class with a private constructor. So how do we instantiate it from outside of the class?
We define a public and static method inside the class, meaning it’s a method that belongs to the class. To prevent the creation of more than one object, only call the constructor if it hasn’t been called yet. Here’s how the code looks like.
The only problem with instantiating objects using “new” is that it’s tightly coupled because it means we’re dependant on a concrete class. The code is not flexible, adding new classes would be cumbersome. In some cases, we want to define several classes with similar properties, so we use an abstract class and the other classes implement the abstract class. But we still need to call the concrete classes to instantiate them!
With the factory pattern, we don’t call a constructor. We call a factory method, which produces objects. The code involves a factory (see Dialog) with its sub-factories (see WindowsDialog and WebDialog). When a method of a sub-factory is called, the factory calls the object creation method for it.
Structural patterns allow us to compose objects and classes into larger structures efficiently.
Imagine we have several classes that implement different data structures, and we need to traverse through the data and access the elements without enough time to change the data structures. Writing a different code for each of the data structure is too inconvenient. This is when the iterator pattern comes in handy. With iterator, we can simply check the existence of an element with hasNext() and access it with next().
Composite pattern allows us to compose objects into tree-like structures and traverse through the items flexibly. This is a good choice if your objects can be represented as a hierarchy. The pattern involves Component, Composite, and Leaf.
Think of the Component as the “root”. It’s an interface that holds general operations of the tree. A Composite has “children” which can be other Composites or Leafs. It overrides some methods from its parent Component or delegates work to its sub-element. On the other hand, a Leaf doesn’t have any child. Since it can’t delegate work, it does most of the work.
These patterns are concerned about how objects and classes interact with each other.
Think of the YouTuber-subscriber relationship. As a subscriber, you’d want to keep updated whenever your favourite YouTuber uploads a video. This is exactly the main idea behind the Observer Pattern. It’s basically a one-to-many relationship where one publisher can have multiple subscribers. Whenever the publisher class is updated, the observers are notified. An object can choose to subscribe or unsubscribe to the publisher.
When doing an action, there are often cases where there are a number of states the client can be in. For instance, imagine we’re ordering a product in an e-commerce platform. After checking out the products in our cart, the state is “order created” or “waiting for payment”. Then we complete the payment successfully. Now the state is “payment confirmed” or “waiting for seller to send product”. When the seller sends the product to our house, the state becomes “product is shipped”. If the product arrives safely, we then change the state to “product arrived”.
Instead of writing a long code of conditional statements or switch cases, the solution is to create classes of the possible states. The main object has a “state” attribute that can be set to one of the state classes. Take a look at a very simple implementation of this pattern.
So those are some design patterns that I hope you found useful. Thanks for reading!
- Head First Design Patterns by Elisabeth Freeman and Kathy Sierra