Understanding Creational Design Patterns

The last part in our series

Welcome to the third and final part of our series on understanding design patterns in iOS development. In previous blog posts, we explored structural and behavioral design patterns. In this post, we’ll take a look at a few examples of creational patterns, which allow us to create interfaces in our applications.

🔨 The Builder Pattern

This pattern is composed of three types: the director, the product, and the builder. The director takes the inputs and coordinates with the builder. The product is the complex object we are trying to create. And the builder accepts step-by-step instructions and handles the creation of the product.

To learn more about the builder pattern, I recommend reading John Sundell’s explanation. He covers the core ideas behind the pattern, pros and cons of using it, and a few examples we can look at.

🏭 The Factory Pattern

This pattern allows us to create objects without exposing how they are made. We can use this method when we are not sure what exact types and dependencies the code should work with.

We can also use this method if we want to give users of our framework a way to extend our internal components. This method is beneficial because it allows us to avoid tight coupling between the creator of the object and the concrete products. It follows the single responsibility principle by moving the object creation code to one place, ensuring that our object only has one responsibility. It also adheres to the open/closed principle, because it allows us to introduce new types into our code without breaking existing code.

📏 The Prototype Pattern

This a creational pattern that allows an object to clone itself. It’s especially useful when creating a new instance of an object is expensive. Further, this method is useful because it allows all the properties and methods to be carried over when cloned, which is useful when subclassing is not practical.

You can give default values to the type which will be cloned and set these values after cloning. The prototype pattern is beneficial because it allows you to clone objects without coupling to their concrete classes, and complex objects may be created more conveniently.

1️⃣ The Singleton Pattern

Before exploring the world of singletons, I thought they were the greatest thing ever created. By creating a singleton, we ensure that a class has only one instance, allowing us to control access to some shared resource. It also provides a global access point to that instance.

Some benefits to this pattern are:

  • The singleton instance is created only when it is requested for the first time.
  • You gain access to the instance globally.

However, some drawbacks to this pattern are:

  • It is not very thread-safe.
  • It can be difficult to test.

In the snippet above, we create a class with a private initializer—private, because we only want there to be one instance of this class. We created the property shared which will be the only point of access to the instance of this class.

🩹 Dependency Injection

An alternative to the Singleton pattern would be dependency injection. As the name suggests, we are injecting our objects with the dependencies they need to function.

Injecting your objects with its dependencies has a major advantage because it makes our objects fully testable. The easiest example of this comes from mocking/stubbing our networking layer.

Constructor/Initializer Injection

In this method of injection, we pass the dependency on initialization of the class. We also will need to create a read-only property inside of the class which will store a reference to the initialized property.

Property Injection

We can use this property in cases where we do not have access to the object’s initialization but we do have access to how the desired property is initialized.

In the below snippet of the iceCreamDetailedViewController we have a property name which is set when the mainViewController segues here.

Method Injection

Consider this method if you only want to use this property once. You can pass the dependency as a parameter of a method.

Ambient Context

This method should be used for universal dependencies that are being shared alongside multiple object instances, logging, and analytics of a caching mechanism. It should only be used sparingly, because it could cause implicit dependencies and global mutable state.

👋 That’s all, folks!

This concludes our three-part series on exploring design patterns in iOS development. If you missed the other two, or just want a refresher, go back and take a look at Understanding Structural Design Patterns and Understanding Behavioral Design Patterns for more.