Enhance Factory Pattern capabilities for DI in Swift within 3 minutes

Implementing Factory Pattern in Swift

Page content

Factory pattern is one of the common patterns in a programming language. It is one of the creational design patterns that provide a high level of flexibility for your code. One of the interesting use cases that could make Factory pattern become a handy tool to use is Dependency Injection. Normally whenever we want to inject an interface as a dependency on an object, we could easily pass it through the initializer or set it from the property. See the example below

1class SampleViewModel {
2  private var networkManager: NetworkProtocol
3  private var databaseManager: DatabaseProtocol
4  init(networkManager: NetworkProtocol, databaseManager: DatabaseProtocol) {
5      self.networkManager = networkManager
6      self.databaseManager = databaseManager
7  }
8}

In the above example, we are trying to inject the SampleViewModel with dependencies of network and database manager (Remember, always depend on abstraction). It is the most simple way to do DI over initializer, and problems will start to occur once we have multiple dependencies and need to inject too many parameters. Of course, then we try the second approach like below.

1class SampleViewModel {
2  typealias Dependencies = HasNetwork & HasDatabase
3  private var dependencies: Dependencies
4  init(dependencies: Dependencies) {
5      self.dependencies = dependencies
6  }
7}

In the second approach, we simply pass a typealias with the rules if both of the dependencies are encapsulated into an object that conforms to both of them, which is called protocol composition. It is a very swifty way and looks so neat. There will be no problem until then we need to pass every single dependency to the initializer or we could end up with multiple & operator into our typealias. How about if we are using one object that could easily produce this for us? Yes! that’s the factory. See below example

1class SampleViewModel {
2  private var factory: ConfigurationFactory = ConfigurationFactory()
3  init() {
4      self.dependencies = factory.createDependenciesOne()
5  }
6}

See Option 3, we could just have a factory instance, and then call out the function to create the dependencies for us. See the below picture for the big picture of our Factory class. It has the function of creating the dependencies for us, or either provide a function to create a singular object of some dependency.

Below is the complete code

 1protocol NetworkProtocol {}
 2protocol DatabaseProtocol {}  
 3
 4protocol HasNetwork {
 5  var network: NetworkProtocol { get }  
 6}
 7
 8protocol HasDatabase {
 9  var database: DatabaseProtocol { get }  
10}  
11
12final class ConfigurationFactory {
13    func createNetworkManager() -> NetworkProtocol {
14      return NetworkManager()
15    }
16
17    func createDatabaseManager() -> DatabaseProtocol {
18      return databaseManager()
19    }
20
21    typealias DependenciesOne = HasNetwork & HasDatabase
22    func createDependenciesOne() {
23      return ObjectWithThatTwoDependencies()
24    }
25}

TIPS & TRICK

  1. You can create a singleton factory in case you want to make one factory that can easily accessible by any object. However, bear in mind, in order to keep our singleton not become an anti-pattern, as most people try to avoid it, keep it stateless, only for creating a necessary object. By creating our singleton stateless and only for producing an object, then it’s fine, don’t need to worry about testing. Remember, the problem of a singleton in DI mostly, because of singleton can produce a shared state and provide a stateful class. So keep it clean.

  2. Another trick we can create a Container, and this container can conforms to multiple Factory, then store this container into the top-level object of your application, or even make it a singleton if needed, and this Container is the only one assemble most of Factory protocol that could produce multiple objects that needed by its accessor.

  3. Explore more about Resolver pattern. This pattern would be a great example of how Factory pattern can be a powerful tool for DI. Swinject is one example of how Resolver pattern works and how Factory pattern is working under the hood.

Container Example

 1class DependenciesContainer: HasNetwork, HasDatabase {
 2  var database: DatabaseProtocol {
 3    return DatabaseManager(type: .coreData)
 4  }
 5  var networkManager: NetworkProtocol {
 6    return NetworkManager(urlSession: .shared) // or .inMemory
 7  }
 8}
 9
10extension DependencyContainer: ViewModelFactory {
11  func makeSampleViewModel() -> SampleViewModel {
12    return SampleViewModel(dependencies: self)
13  }
14}

That’s all for today’s knowledge about factory patterns. Hope it is very helpful in addition of one of the most powerful Creational Design Pattern. Stay tuned for the next article!