Lightweight Design Patterns in iOS (Part 4) - Factory

Design Patterns are part of Mobile Development for a while now and a revolution towards Quality Assurance is on. Such patterns have become a standard among the community, yet implementing an academic one might come with a price โ€” complexity.

Because some Design Patterns solve very complex problems, they often result in a complicated implementation as well. So complex sometimes, that we tend to use heavy Third Party Frameworks, even for simple use cases.

The time has come to combine quality and simplicity.

๐ŸŽฌ Hi there, Iโ€™m Jean!

Previously on the series Lightweight Design Patterns in iOS, we have covered the case of the Coordinator Pattern or how to implement a lightweight navigation flow! ๐Ÿšฆ
In this episode, we will take a look at the Factory Pattern, the problem it solves as well as how to implement it in a simple, yet powerful manner! ๐Ÿญ

Factory Pattern


The role of the Factory is to instantiate new objects along with their dependencies. ๐Ÿ’‰
This means in iOS, instantiating a ViewController from a XIB or StoryBoard as well as injecting the ViewModel and the Coordinator for instance. ๐Ÿ›ฃ
The advantage of such a pattern is mainly to extract Instantiation Logic from any other layer (ViewController, ViewModel, Coordinatorโ€ฆ) as sometimes a lot of dependencies need to be injected. ๐Ÿ˜ฑ
Also, the Factory Pattern gives us a really nice way to define the Dependency Tree! ๐Ÿ’ฏ

Letโ€™s now look at an example! ๐Ÿ‘€

The Factory in practice


We have an app, which displays a list of โ€œthingsโ€ preceded by a picture. Easy. ๐Ÿ‘

In order to keep all our dependencies centralized, letโ€™s first create a DependencyManager! This dependency manager will contain by essence a NetworkManager for the network calls. ๐Ÿ‘Œ

struct DependencyManager {
    let networkManager: NetworkManagerProtocol
}

Now, from this NetworkManager, we might want to fetch different kinds of Data, it could be an Image, or a Somethingโ€ฆ it doesnโ€™t matter: we will implement a new Manager, responsible for the parsing logic, for each use case. โœ…

Looking at an ImageManager for instance, this would mean creating an extension on DependencyManager in order to make an ImageManager, by injecting the NetworkManager in it! ๐Ÿ’‰

And here is our first Factory: ImageFactory! ๐Ÿญ

protocol ImageFactory {
    func makeImageManager() -> ImageManagerProtocol
}

extension DependencyManager: ImageFactory {
    func makeImageManager() -> ImageManagerProtocol {
        return ImageManager(networkManager: networkManager)
    }
}

Extending it to the use case of fetching โ€œSomethingโ€, we would make the SomethingManager the exact same way: SomethingFactory is born! ๐Ÿ‘ถ

protocol SomethingFactory {
    func makeSomethingManager() -> SomethingManagerProtocol
}

extension DependencyManager: SomethingFactory {
    func makeSomethingManager() -> SomethingManagerProtocol {
        return SomethingManager(networkManager: networkManager)
    }
}

Looking fit! ๐Ÿ’ช

Now, a Factory does not have to limit itself to instantiating Managers; ViewControllers are great candidates too! ๐Ÿš€

Going back to the example, making a SomethingViewController would be as simple as instantiating it from a StoryBoard or a XIB first, and then injecting the necessary dependencies (i.e. a ViewModel) through the attributes! ๐Ÿ’‰

protocol ViewControllerFactory {
    func makeSomethingViewController() -> SomethingViewController
}

extension DependencyManager: ViewControllerFactory {
    func makeSomethingViewController() -> SomethingViewController {
        let viewController = SomethingViewController.storyBoardInstance
        viewController.viewModel = SomethingViewModel(factory: self)

        return viewController
    }
}

Now, if you pay attention to the way the ViewModel has been instantiated above, youโ€™ll realize that the Managers are not injected directly, but the Factory itself is! ๐Ÿคฏ

This is really nice to keep the Dependency Definition inside the Entity. ๐Ÿ‘Œ
That way, when a refactor happens and a new Dependency needs to be injected, only the ViewModel and the corresponding Factory will be affected, not the ViewControllerFactory! ๐Ÿ˜Ž
More separation of concerns = less problems ๐Ÿ˜‰

Now, looking back at our SomethingViewModel, we can use the famous Protocol Composition trick to define exactly which use case the factory should have access to! ๐Ÿ‘ฎ
In our case, we define the typealias Factory as being an ImageFactory and SomethingFactory. That way, injecting the Managers can be done very elegantly and in a lazy manner! ๐Ÿ’‰

final class SomethingViewModel {
    
    typealias Factory = ImageFactory & SomethingFactory
    private let factory: Factory

    private lazy var imageManager: ImageManagerProtocol = factory.makeImageManager()
    private lazy var somethingManager: SomethingManagerProtocol = factory.makeSomethingManager()

    init(factory: Factory) {
        self.factory = factory
    }
  
    ...
}

And boom! ๐Ÿ’ฅ
We just made a lightweight Factory! ๐ŸŽ‰

Was it worth it? Did that help you see what problems the Factory Pattern tries to solve? How to make such a pattern lightweight?

If so, follow me on Twitter, Iโ€™ll be happy to answer any of your questions and youโ€™ll be the first one to know when a new article comes out! ๐Ÿ‘Œ

Bye bye! ๐Ÿ‘‹