2016-07-27 2 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 
    } 
} 

Его Persistable протокол выглядит следующим образом:

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

И мой Repository код выглядит примерно так:

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 нг ошибка в моей ZombieServiceProtocol:

  • Не может специализироваться необщего типа «RepositoryProtocol»

Я получаю следующее сообщение об ошибке в моем ZombieService:

  • Не может специализироваться без универсального типа «RepositoryProtocol '
  • Member' 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) 
    } 
} 

Этот код также не компилируется в настоящее время с теми же ошибками, что и выше.

Я смотрел на связанные типы и теги типаAlias, а также на Generics Manifesto. Рассматривая Манифест, я считаю, что это относится к разделу «Общие протоколы», который в настоящее время отмечен как «Невероятно» (который меня избивает). Если я не смогу выполнить что-то вроде того, что я пытаюсь сделать выше, что будет лучшим решением?

+0

Глядя, как звучит no? Попытка и рефакторинг, чтобы увидеть, что еще я могу сделать ... – 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 можно сделать замену (неработоспособных) общих протоколов с нестандартными классами. Пример:

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