Migrating class extensions inheritance clause to protocol extensions

Viewed 899

I've had a VehicleModels framework with classes like Car, Bike, Plane. In the other framework VehicleInventory I needed to print customised descriptions (specific to the second framework) in a table. So I have added protocol DescriptableVehicle with method describe(). Then I've added protocol extensions for all the vehicles like:

extension Car: DescriptableVehicle {
  func describe() -> String {
    return "Car: \(self.vin)" // returns formatted vehicle number
  }
}

However, assumptions have changed and now I do not expose concrete classes from my vehicles framework. Instead, I expose protocols like CarProtocol, BikeProtocol, so that in general I have the same information.

The problem is that I can't use protocol extensions anymore (or at least not in that shape) because extension of protocol in opposite to extension of class cannot have an inheritance clause.

Any idea how I can tackle the problem to not modify my usages to much? Initially, I thought couple where clauses on protocols plus couple casts will make the deal, however without access to classes it doesn't help. I have also tried Adapters and type erasure but either I am using it badly or it serves a different purpose.

To illustrate problem I have prepared repository: https://github.com/wedkarz/SwiftProtocolExtensionsProblem

There are two playgrounds. V1 is what I used to have and which was working. V2 contains what I have now and what I am trying to make working.

In real life, class PrivateFramework is a separate framework and protocols: VehicleProtocol, CarProtocol, BikeProtocol, PlaneProtocol are part of it, but DescriptableVehicle is not a part of PrivateFramework, so it cannot be used inside. The example illustrates a problem with accesses to concrete types and a problem with its extensions.

You can see there are extensions commented out because I cannot use them anymore. The goal is to make the collection of [VehicleProtocol] printing its contents in a similar fashion it was working previously.

1 Answers

Does this answer your question?

class PrivateFramework {
    private class AbstractVehicle: VehicleProtocol {
        var name: String { return "Abstract vehicle name" }
    }

    private class Car: AbstractVehicle, CarProtocol {
        override var name: String { return "Car vehicle name" }
        let vin: String = "ABCDEFGH VIN"
    }

    private class Bike: AbstractVehicle, BikeProtocol {
        override var name: String { return "Bike vehicle name" }
        let saddle: String = "Comforable saddle name"
    }

    private class Plane: AbstractVehicle, PlaneProtocol {
        override var name: String { return "Plane vehicle name" }
        let numberOfEngines: Int = 4
    }

    func produceVehicles() -> [DescriptableVehicle] {
        let car = Car()
        let bike = Bike()
        let plane = Plane()
        return [car, bike, plane]
    }
}

protocol VehicleProtocol {
    var name: String { get }
}

protocol DescriptableVehicle: VehicleProtocol {
    func describe() -> String
}



protocol CarProtocol: DescriptableVehicle {
    var vin: String { get }
}

protocol BikeProtocol: DescriptableVehicle {
    var saddle: String { get }
}

protocol PlaneProtocol: DescriptableVehicle {
    var numberOfEngines: Int { get }
}




extension DescriptableVehicle where Self: CarProtocol {
    func describe() -> String { return "Car, \(self.name), \(self.vin)" }
}

extension DescriptableVehicle where Self: BikeProtocol {
    func describe() -> String { return "Bike, \(self.name), \(self.saddle)" }
}

extension DescriptableVehicle where Self: PlaneProtocol {
    func describe() -> String { return "Plane, \(self.name), \(self.numberOfEngines)" }
}

let vehicles: [DescriptableVehicle] = PrivateFramework().produceVehicles()

vehicles.forEach { print($0.describe()) }
Related