3
votes

My data access layer consists of a generic Repository protocol

protocol Repository {
    associatedtype T
    func getAll() -> Promise<[T]>
}

and its concrete implementation:

class FirebaseRepository<T: Model>: Repository {
    func getAll() -> Promise<[T]> {
        fatalError()
    }
}

Basically, Repository can be RestRepository, FirebaseRepository, PlistRepositry etc. Repository is used by the business logic:

/// My business logic
class ModelService<T: Repository> {
    private let repository: T

    public init(repository: T) {
        self.repository = repository
    }
}

The problem comes when I'm trying to apply a factory pattern to a repository. Here's what I came in first:

/// Returns a concrete Repository implementation
class RepositoryFactory {
    func makeRepository<T: Model>(type: T.Type) -> Repository {
        return FirebaseRepository<T>()
    }
}

and this definitely gets a compiler error:

Protocol 'Repository' can only be used as a generic constraint because it has Self or associated type requirements

The only viable option I came to is this:

func makeRepository<T: Model>(type: T.Type, U: Repository) -> U {
    return FirebaseRepository<T>() as! U
}

but as you understand, the force optional unwrapping is not acceptable in the production code.

How to make protocols with associated types work with factory design pattern?

1

1 Answers

2
votes

You can use Type erasure. Here is an example:

protocol CustomProtocol {
    associatedtype AssociatedType
    func foo(argument: AssociatedType)
    func bar() -> AssociatedType
}

If you want to use CustomProtocol directly, you will receive your error:

let array = [CustomProtocol]()

Protocol 'CustomProtocol' can only be used as a generic constraint because it has Self or associated type requirements

So you can make the same trick, as Swift does with their sequences:

public struct AnyCustomProtocol<T>: CustomProtocol {
    func bar() -> T {
        fatalError("Needs implementation")
    }
    func foo(argument: T) {

    }
}

let array = [AnyCustomProtocol<Any>]() // works fine

Your problem solution in this case will look somehow like this:

    class Promise<T> {

    }

    protocol Model {

    }

    protocol Repository {
        associatedtype T
        func getAll() -> Promise<[T]>
    }

    class FirebaseRepository<T: Model>: AnyRepository<T> {
        override func getAll() -> Promise<[T]> {
            fatalError()
        }
    }

    class AnyRepository<T>: Repository {
        func getAll() -> Promise<[T]> {
            fatalError()
        }
    }


    class RepositoryFactory {
        func makeRepository<T: Model>(type: T.Type) -> AnyRepository<T> {
            return FirebaseRepository<T>()
        }
    }

__

For further reading you can check this and Official docs on Generics