是否可以将通用协议传递到构造函数中以在 Swift 3 中进行适当的依赖注入?
Is it possible to pass generic protocols into a constructor for proper Dependency Injection in Swift 3?
我正在尝试使用泛型在 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)
}
}
}
僵尸 class 看起来像这样:
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'
- 成员'insert'不能用于协议类型的值
'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。在查看宣言时,我相信这属于 "Generic Protocols" 部分,该部分目前被标记为不太可能(这让我很沮丧)。如果我无法完成上面我尝试做的事情,下一个最佳解决方案是什么?
您在当前 Swift 中可以做的一件事是用通用的非最终 classes 替换(非工作的)通用协议。示例:
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 中很可能是非惯用的(基础 class 中的存根方法),但它有效。
你的问题的答案是肯定的,这绝对是可能的,只是它目前需要一些与 PAT 相关的东西 "magic"。使用 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!
我正在尝试使用泛型在 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)
}
}
}
僵尸 class 看起来像这样:
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'
- 成员'insert'不能用于协议类型的值 '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。在查看宣言时,我相信这属于 "Generic Protocols" 部分,该部分目前被标记为不太可能(这让我很沮丧)。如果我无法完成上面我尝试做的事情,下一个最佳解决方案是什么?
您在当前 Swift 中可以做的一件事是用通用的非最终 classes 替换(非工作的)通用协议。示例:
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 中很可能是非惯用的(基础 class 中的存根方法),但它有效。
你的问题的答案是肯定的,这绝对是可能的,只是它目前需要一些与 PAT 相关的东西 "magic"。使用 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!