Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protocols with associated type and factory pattern?

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?

like image 948
Andrey Gordeev Avatar asked Oct 19 '25 13:10

Andrey Gordeev


1 Answers

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

like image 92
fewlinesofcode Avatar answered Oct 21 '25 22:10

fewlinesofcode