如何实现符合类型限制的协议的通用基类,并为这些通用类型实现一个工厂?

3

我正在尝试在我的项目中实现仓储模式(repository pattern)。我希望该设计足够灵活,以便将来可以更换底层实现。例如,我想支持 Realm,但如果需要,也能够更换为 Core Data。我还想为测试实现伪造对象(fakes)。

我从伪造对象(fakes)开始实现,并且我想要实现一个具有泛型约束并符合 Repository 协议的 FakeRepository 基类。

请注意,我省略了一些实现细节,以使文章尽可能简短。

protocol Repository {
    associatedtype Item

    var count: Int { get }

    func item(at index: Int) -> Item
}

class FakeRepository<Model>: Repository where Model: Identifiable {
    private var items: [Model] = []

    var count: Int { items.count }

    func item(at index: Int) -> Model {
        return items[index]
    }
}

我还希望为我打算支持的每个模型定义一个协议,例如UserRepository。

struct User: Identifiable {
    let id: Int
    let name: String
}

protocol UserRepository: Repository where Item == User { }

现在我只需要定义一个具体的FakeUserRepository就可以了,因为所有的工作都已经完成,无需实现任何内容。

class FakeUserRepository: FakeRepository<User>, UserRepository { }

我的问题是在工厂模式的实现上。我想要做的是像这样,但是工厂协议无法编译,因为UserRepository有相关联的类型要求。

protocol UserRepositoryFactory {
    func makeRepository() -> UserRepository // DOES NOT COMPILE
}

struct FakeUserRepositoryFactory: UserRepositoryFactory {
    func makeRepository() -> UserRepository {
        return FakeUserRepository()
    }
}

struct CoreDataUserRepositoryFactory: UserRepositoryFactory {
    func makeRepository() -> UserRepository {
        return CoreDataUserRepository()
    }
}

接下来,我想用一个依赖注入容器来管理所有的工厂。

struct DependencyContainer {
    let userRepositoryFactory: UserRepositoryFactory
}

我尝试过的另一种解决方案如下:

protocol Repository2 {
    associatedtype Item
    func item(at index: Int) -> Item
}

class Repository2Base<Model>: Repository2 {
    func item(at index: Int) -> Model {
        fatalError()
    }
}

class FakeRepository2<Model>: Repository2Base<Model> {
    var items: [Model] = []
    override func item(at index: Int) -> Model {
        return items[index]
    }
}

protocol UserRepositoryFactory2 {
    func makeRepository() -> Repository2Base<User>
}

class FakeUserRepositoryFactory2: UserRepositoryFactory2 {
    func makeRepository() -> Repository2Base<User> {
        return FakeRepository2<User>()
    }
}

现在这个代码可以编译并且能正常工作,但是我不喜欢必须调用fatalError()才能编译通过的方式,这似乎是一种hack。有没有更优雅的方式来实现我的目标?


嗨,罗布,很抱歉在这里打扰你,但我真的需要你的帮助!:D 请检查你的领英。 - Chris
1个回答

1
我不喜欢将继承和泛型混合使用,这也是你必须调用fatalError()的原因。
请尝试以下操作:
1.首先创建Repository协议。
protocol Repository {
    associatedtype Item: Identifiable
    func item(at index: Int) -> Item
    init() 
}

class FakeRepository<Item: Identifiable>: Repository {
    var items: [Item]
    func item(at index: Int) -> Item {
        return items[index]
    }
    
    required init() { // notice that it it required
        items = []
    }
}

我在这里添加了init(),因为我希望FakeRepositoryFactory能够创建任何存储库。

  1. 然后工厂:

public class SomeFakeRepositoryFactory {
    public init() { }
    public func makeRepository<Repo: Repository>() -> Repo {
        return Repo()
    }
}


你可以这样使用它:


struct User: Identifiable {
    let id: Int
    let name: String
}

struct Car: Identifiable {
    let id: Int
    let name: String
}


let user = User(id: 1, name: "Robert")
let factory = SomeFakeRepositoryFactory()
var userRepository: FakeRepository<User> = factory.makeRepository()
userRepository.items = [user]
userRepository.items.forEach { print($0.name) } // prints "Robert"

let car = Car(id: 1, name: "Delorean")
var carRepository = factory.makeRepository() as FakeRepository<Car>
carRepository.items = [car]
carRepository.items.forEach { print($0.name) } // prints Delorean


目前我认为实现我想要的是不可能的。我需要一个更多态的实现,其中存储库和工厂类型不需要在编译时知道。相关的类型要求使真正的工厂模式实现变得不可能。 - Rob C
哦!等一下 :) - AnderCover
@RobertCrabtree 这样是否更好?工厂类型仍然需要在编译时知道,但是工厂不再绑定于单一类型的存储库。 - AnderCover
理想情况下,工厂方法不应该有任何存储库类型的限制。这仍然是一个编译时解决方案。谢谢,但我认为这不可能。 - Rob C
1
当然,如果您想让工厂创建存储库,那么您必须在某个时候告诉编译器。 - AnderCover

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接