2016-07-27 9 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

相關問題