【问题标题】:Is it possible to pass generic protocols into a constructor for proper Dependency Injection in Swift 3?是否可以将通用协议传递给构造函数以在 Swift 3 中进行正确的依赖注入?
【发布时间】:2016-08-05 20:56:34
【问题描述】:

我正在尝试使用泛型在 Swift 3 中完成面向协议的编程。这还不完全支持吗?我将在下面向您展示我想做的事情,但不会编译。我在这里错过了什么吗?我的目标是能够使用面向协议的编程来执行依赖注入,以便在我的单元测试中轻松地模拟这些结构。

protocol ZombieServiceProtocol {

    func fetchZombies()
    var zombieRepository: RepositoryProtocol<Zombie> { get set }
}

struct ZombieService: ZombieServiceProtocol {

    var zombieRepository: RepositoryProtocol<Zombie>

    init(zombieRepository: RepositoryProtocol<Zombie>) {
        self.zombieRepository = zombieRepository
    }

    func fetchZombies() {
        self.zombieRepository.deleteAll()
        self.createFakeZombies()
    }

    private func createFakeZombies() {
        for index in 1...100 {
            let zombie = Zombie(id: index, name: "Zombie \(index)")
            self.zombieRepository.insert(zombie)
        }
    }
}

Zombie 类如下所示:

public struct Zombie: Persistable {

    var id: Int
    let name: String?
    init(id: Int, name: String?) {
            self.id = id
            self.name =name
    }
}

其 Persistable 协议如下所示:

protocol Persistable {  
    var id: Int { get set }
}

我的存储库代码如下所示:

protocol RepositoryProtocol: class {
    associatedtype Object: Persistable

    //...

    func insert(_ object: Object) -> Void
    func deleteAll(_ predicate: (Object) throws -> Bool) -> Void
}

class Repository<Object: Persistable>: RepositoryProtocol {

    var items = Array<Object>()

    //...

    func insert(_ object: Object) {
        self.items.append(object)
    }

    func deleteAll() {
        self.items.removeAll()
    }

}

我在 ZombieServiceProtocol 中收到以下错误:

  • 无法专门化非泛型类型“RepositoryProtocol”

我在 ZombieService 中收到以下错误:

  • 无法专门化非泛型类型“RepositoryProtocol”
  • 成员“插入”不能用于协议类型的值 '存储库协议';改用通用约束

为了准确突出我想要完成的任务,下面是一个简单的测试,我在其中创建了一个 Mock 存储库并尝试在我的 ZombieService 中使用它而不是真实的:

@testable import ZombieInjection
class ZombieServiceTests: XCTestCase {

    private var zombieRepository: RepositoryProtocol<Zombie>!
    private var zombieService: ZombieServiceProtocol

    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
        self.zombieRepository = RepositoryMock<Zombie>()
        self.zombieService = ZombieService(zombieRepository: self.zombieRepository)
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }

    func testExample() {
        // Arrange
        // Act
        self.zombieService.fetchZombies()

        // Assert
        XCTAssert(self.zombieRepository.count() > 0)
    }
}

此代码目前也无法编译,并出现与上述相同的错误。

我一直在查看 associatedTypes 和 typeAlias 标签以及Generics Manifesto。在查看宣言时,我相信这属于“通用协议”部分,该部分目前被标记为不太可能(这让我很生气)。如果我无法完成我在上面尝试做的事情,那么下一个最佳解决方案是什么?

【问题讨论】:

  • 看起来像一个响亮的不?打算尝试重构,看看我还能做什么......

标签: swift generics dependency-injection swift-protocols


【解决方案1】:

您的问题的答案是肯定的,这绝对是可能的,只是它目前需要一些与 PAT 相关的“魔法”。使用 Swift3 和 Xcode 8.0 beta 4,您应该能够在 Playground 中运行以下内容:

protocol Persistable {
    var id: Int { get set }
}
protocol RepositoryProtocol: class {
    associatedtype Object: Persistable
    func insert(_ object: Object) -> Void
    func deleteAll()
}
protocol ZombieServiceProtocol {
    associatedtype RepositoryType: RepositoryProtocol
    var zombieRepository: RepositoryType { get set }
    func fetchZombies()
}
public struct Zombie: Persistable {
    var id: Int
    let name: String?
}

// Mocks
class RepositoryMock<Object: Persistable>: RepositoryProtocol {
    func insert(_ object: Object) { print("look, there's another one!")}
    func deleteAll() { print("it's safe out there, all zombies have been deleted") }
}
struct ZombieServiceMock<RepositoryType: RepositoryProtocol
                    where RepositoryType.Object == Zombie>: ZombieServiceProtocol {
    var zombieRepository: RepositoryType
    init(zombieRepository: RepositoryType) {
        self.zombieRepository = zombieRepository
    }
    func fetchZombies() {
        self.zombieRepository.deleteAll()
        self.createMockZombies()
    }
    private func createMockZombies() {
        for index in 1...5 {
            let zombie = Zombie(id: index, name: "Zombie \(index)")
            self.zombieRepository.insert(zombie)
        }
    }
}

// Tests
class ZombieServiceTests<RepositoryType: RepositoryProtocol,
                         ServiceType: ZombieServiceProtocol
                                where ServiceType.RepositoryType == RepositoryType> {
    private var zombieRepository: RepositoryType
    private var zombieService: ServiceType

    init(repository: RepositoryType, service: ServiceType) {
        zombieRepository = repository
        zombieService = service
    }
    func testExample() {
        self.zombieService.fetchZombies()
    }
}
let repositoryMock = RepositoryMock<Zombie>()
let repositoryService = ZombieServiceMock(zombieRepository: repositoryMock)
let zombieTest = ZombieServiceTests(repository: repositoryMock, service: repositoryService)
zombieTest.testExample()

// Prints:
// it's safe out there, all zombies have been deleted
// look, there's another one!
// look, there's another one!
// look, there's another one!
// look, there's another one!
// look, there's another one!

【讨论】:

  • 这让我离我想要的更近了!谢谢!
【解决方案2】:

在当前的 Swift 中你可以做的一件事是用通用的非最终类替换(非工作的)通用协议。示例:

class RepositoryBase<Object: Persistable> {
    func insert(_ object: Object) -> Void {
        fatalError()
    }
    func deleteAll(_ predicate: (Object) throws -> Bool) -> Void {
        fatalError()
    }
}

struct ZombieService: ZombieServiceProtocol {
    var zombieRepository: RepositoryBase<Zombie>
    // ...
}

这样的代码在 Swift 中可能不是惯用的(基类中的存根方法),但它可以工作。

【讨论】:

  • 我真的想避免这种工作......现在要尝试其他答案。
猜你喜欢
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-13
相关资源
最近更新 更多