Engineering

Understanding Structural Design Patterns

What they are, and how to use them

If you are like me and have a year or less of experience developing iOS apps, there might be some concepts that you use in code without realizing what they are or why you’ve used them—I’m talking about design patterns. These patterns allow our app’s codebase to be more readable and resilient, help guide us when structuring our apps, and allow us to create objects and govern how those objects communicate with each other. Chances are, you’ve used many of these patterns without realizing it—like MVC, MVVM, Singleton, and Observer.

Design patterns are quite powerful if you know what to use and when to use it. I set out on a journey to explore some of the various design patterns used in iOS development and gain a better understanding of them.

This is the first of a three-part series on design patterns. In this installment, we will be looking at structural patterns. Structural patterns allow us to more easily design the relationships of our application components. Below, we’ll take a look at six structural patterns, with a code example for each one. Let’s dive in together!

🔌 The Adapter Pattern

This pattern is similar to using adapters in our daily life—for example, if you want to connect a USB-A cable to a USB-C port, you need an adapter to change the connector type of the USB-A cable into one the USB-C port can recognize. Similarly, we can use the adapter pattern to allow incompatible interfaces to communicate with each other. We can also use it when working with third-party libraries that are incompatible with our existing code.

The adapter pattern is beneficial because it abstracts business logic to make our interfaces work together more seamlessly, and allows for the ability to introduce new kinds of adapters without breaking existing code. The drawback, however, is that it increases code complexity by introducing a set of new interfaces and classes.

🖌 The Decorator Pattern

This pattern allows us to add functionality to an object without changing its fundamental components. It’s like getting dressed—we change our outfits daily, but these changes have no effect on our composition as humans.

The decorator pattern allows us to extend the behavior of objects without making a new subclass. Further, we can divide a large class that implements many behaviors into several smaller ones.

🎂 The Facade Pattern

I think of the facade pattern as a bakery: so many things happen behind the scenes at a bakery, but when a customer comes in, they only see the beautifully designed pastries and cakes.

This pattern is designed to provide a simplified interface to a complex subsystem. It’s like Paul Hudson says in his book Swift Design Patterns:

A Good Facade:

  • Makes complex APIs easier to read, understand, and use, because their complexity is wrapped up in simpler methods.
  • Reduces dependencies on the internals of those APIs, which allows you to change them freely as long as the end result does the same job.
  • Lets you wrap a bad architecture in something that works better based on experience, or a complex architecture in something simpler that does the job for most people.

🎨 Model View Controller

This is the first pattern I learned when I started iOS development. It involves separating app components into models, views, and controllers.

Models are a representation of our underlying data, containing all the properties and methods which define a type of object; views are responsible for displaying information from the controller; and the controller is responsible for mediating views and models. When the user interacts with a view, the controller interprets this information and alerts the model to update accordingly.

While using this pattern, I’ve run into things in my applications that fall into a grey area, like networking logic—where would that go? Once you start asking yourself questions like that, you should consider reaching for another design pattern.

Below is an example of the MVC pattern in action. We have our model, which represents an IceCream; our controller, containing an array of ice cream flavors; and our view, which takes the information given by the controller and displays it.

🖼 Model-View-ViewModel (MVVM)

One limitation to the MVC pattern is that controllers can get quite large—often jokingly called “Massive View Controller.” This occurs because when we have responsibilities that don’t fall into our model, view, or controller, they tend to end up in our view controller.

The MVVM pattern has been proposed as an alternative, encapsulating our components into models, views, and view models. Like with MVC, models are our data representations; views can be our UIViewController and our UIView subclasses; and view models are used to transform data from our model into a form that can be used by our views.

👩‍🎨 Model-View-ViewModel-Controller (MVVMC)

We employ the MVVMC pattern heavily at Lickability. Before I started, I was unfamiliar with this pattern, but after using it for a few months I have found it to be quite helpful when structuring my applications. I’ve noticed three major differences between this pattern and MVC: improved code encapsulation, improved code readability, and smaller controllers as a result of removing some presentation logic.

When I used to use the MVC pattern, I found myself exposing view objects in order to configure them, not yet aware of the dangers of doing so (like outside entities being able to change the content displayed on these objects). With the MVVMC pattern, each view has its own view model encapsulating the data needed to populate the view, and is the single point of configuration of this view. By doing this, our objects no longer need to have an internal level.

🔮 What’s next

Whew! That was a lot. But this just covers a small selection of all the design patterns out there. In future blog posts, we’ll take a look at a few other types of design patterns: behavioral patterns, and creational patterns. Stay tuned!