denis631
denis631

Reputation: 1865

Swift Protocols with Typealises. Protocol can only be used as a generic constraint

I was thinking about the Validation checks in my app and I thought that calling ValidatorFactory on any model, that implements Validee, meaning saying which class is responsible for ValidatorCreation sounds sweet. But the code below doesn't work :(

Code:

struct Client: Validee {
    typealias ValidatorFactoryClass = ClientValidator
}

protocol Validee {
    associatedtype ValidatorFactoryClass: AbstractValidatorFactory
}

protocol Validator {
    func validate() throws -> Void
}

protocol AbstractValidatorFactory {
    associatedtype Model
    static func create(fromModel model: Model) -> Validator
}

struct ValidatorFactory {
    static func createValidator(fromModel model: Validee) -> Validator {
        return model.ValidatorFactoryClass.create(fromModel: model)
    }
}

struct ClientValidator : AbstractValidatorFactory {
    typealias Model = Client

    static func create(fromModel model: Model) -> Validator {
        return ClientDeliveryAddressValidator(withModel: model)
    }
}

struct ClientDeliveryAddressValidator: Validator {
    typealias Model = Client
    let client: Client

    init(withModel client: Client) {
        self.client = client
    }

    func validate() throws {

    }
}

let client = Client()
do {
    try ValidatorFactory.createValidator(fromModel: client).validate()
} catch {
    // error handling here
}

But even if I forget about the Validator Factory and try to run the following code it doesn't work:

client.ValidatorFactoryClass.create(fromModel: client)

Why ?

Upvotes: 0

Views: 288

Answers (1)

Gary Makin
Gary Makin

Reputation: 3169

If the problem you are having is due to compilation errors, your ValidatorFactory struct needs modifying.

You can’t use a protocol as a type directly. You use it as a constraint on a type. This means than rather than having Validee as a parameter type to ValidatorFactory.createValidator, it is a constraint on that type. This change would give you:

struct ValidatorFactory<T:Validee> {
    static func createValidator(fromModel model: T) -> Validator {
        return model.ValidatorFactoryClass.create(fromModel: model)
    }
}

This still has a problem because Swift cannot work out the relationship between the type T and the parameter type for the call to create. But the relationship exists in your code; you just need to make it explicit.

Changing ValidatorFactory to this compiles for me. I have not tried running the code, however.

struct ValidatorFactory<T:Validee> {
    static func createValidator(fromModel model: T) -> Validator {
        return T.ValidatorFactoryClass.create(fromModel: model as! T.ValidatorFactoryClass.Model)
    }
}

Edit:

Given that Validee already knows about the factory, it would be simpler to change the design so Validee knows about creating the Validator directly.

This would give you:

protocol Validee {
    static func create(fromModel model: Self) -> Validator
}

protocol Validator {
    func validate() throws -> Void
}

struct ValidatorFactory<T:Validee> {
    static func createValidator(fromModel model: T) -> Validator {
        return T.create(fromModel: model)
    }
}

struct Client: Validee {
    static func create(fromModel model: Client) -> Validator {
        return ClientDeliveryAddressValidator(withModel: model)
    }
}

struct ClientDeliveryAddressValidator: Validator {
    typealias Model = Client
    let client: Client

    init(withModel client: Client) {
        self.client = client
    }

    func validate() throws {

    }
}

Upvotes: 1

Related Questions