2016-07-27 12 views
2

我正试图在使用泛型的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) 
     } 
    } 
} 

僵尸类看起来是这样的:

public struct Zombie: Persistable { 

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

它的持久化协议看起来是这样的:

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() 
    } 

} 

我得到了followi NG错误在我ZombieServiceProtocol:

  • 不能专注非泛型类型 'RepositoryProtocol'

我得到了我的ZombieService以下错误:

  • 不能专注非泛型类型“RepositoryProtocol '
  • 成员'insert'不能用于协议类型的值 'RepositoryProtocol';使用一个通用的约束,而不是

,并强调正是我试图完成,这里是一个简单的测试是什么样子的,我创建一个模拟库,并尝试使用,而不是在一个真正的我的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。在看“宣言”时,我认为这属于“通用协议”部分,该部分目前标记为“不太可能”(这是令人沮丧的)。如果我无法完成上述的尝试,那么下一个最佳解决方案是什么?

+0

看起来像是一个响亮的否?去尝试和重构,看看我还能做些什么... – PkL728

回答

2

你的问题的答案是肯定的,这当然需要一些PAT相关的“魔术”。使用Swift3和Xcode 8.0 beta 4,您应该可以在操场上运行以下操作:

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! 
+0

这让我更接近我想要的!谢谢! – PkL728

0

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

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(基类中的存根方法)中是非惯用的,但它可行。

+0

我真的很想避免这种工作......现在去尝试其他答案。 – PkL728

相关问题