3

I'm doing something similar to the Bridge Pattern in JAVA, DriverType is a protocol requires a property named vehicle to be a Drivable object, Drivable is also a protocol and is adopted by the class 'Car'

protocol Drivable {
    var speed: Double { get }
}

protocol DriverType {
    var vehicle: Drivable { get }
}

class Car: Drivable {
    var speed = 80.0;
    var brand = "BMW"
}

class Driver: DriverType {

    var vehicle: Car = Car() //Error: Type 'Driver' does not conform to protocol 'DriverType'

    // This one works, but I have to downcast the property to 'Car' everytime I use it.
    var vehicle: Drivable = Car() //Type 'Driver' does not conform to protocol 'DriverType'
}

When I implement the Driver class, it's very natural to declare vehicle property as a Car. But then I ran into the problem that compiler thinks Driver doesn't conform to DriverType even though Car conforms to Drivable perfectly.

UPDATE:

@Antonio 's answer is solid, but this is what I currently settled, it doesn't involve generic with the class.

protocol Drivable {
    var speed: Double { get }
    init()
}

protocol DriverType {
    func vehicle() -> Drivable
}

class Car: Drivable {
    var speed = 80.0;
    var brand = "BMW"
    required init() {}
}

class Driver: DriverType {
    private var m_vehicle: Car = Car()
    func vehicle() -> Drivable {
        return m_vehicle
    }

    // Inside Driver class I use the m_vehicle property directly
}
Pride Chung
  • 573
  • 7
  • 20
  • Isn't that the same as doing this in your original code: `var vehicle: Drivable = Car()` – Antonio Aug 23 '14 at 12:01
  • No it's not the same. I don't know why Swift treat property and function differently, you can try my edited example in playground. – Pride Chung Aug 23 '14 at 16:06
  • Not sure what you mean by that... but properties and functions *are* different :). By the way, I tried `let vehicle: Drivable = Car()` in playground, as well as your code, and I don't see any difference (besides doing a func call as opposed to reading a property). Maybe there's something else that it's not clear... – Antonio Aug 24 '14 at 04:41
  • My ultimate goal is implment bridge pattern in Swift. Now I can use `m_vehicle` directly inside `Driver` so I don't have to downcast it every time. Since `m_vehicle` is a `Car` which adopts the `Drivable` protocol, I have no problem returning it in the `vehicle` function. – Pride Chung Aug 25 '14 at 01:57

1 Answers1

1

I think the compiler error is misleading. DriverType states that any class adopting it must expose a vehicle property with Drivable type, not a property with a class type adopting the Drivable type.

I would solve this issue by defining both the DriverType protocol and the Car class using generics:

protocol Drivable {
    var speed: Double { get }
    init()
}

protocol DriverType {
    typealias T: Drivable
    var vehicle: T { get }
}

class Car: Drivable {
    var speed = 80.0;
    var brand = "BMW"
    required init() {}
}

class Driver<T: Drivable>: DriverType {   
    var vehicle: T = T() 
}

This explicitly states that classes adopting DriverType must expose a vehicle property whose type is any class adopting the Drivable protocol.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • This answer does work but also has it's limitation, like giving up Objective-C compatibility and can't use swift enum types in `Driver`. Before I can find a perfect solution, I simply change the property vehicle to a function with a backing private stored property. – Pride Chung Aug 23 '14 at 11:36
  • I would consider compatibility with objective-c a limitation, because to enable that you have to limit how far you can go with Swift and all the power it has. Generics is one of them. As a general rule, I always try to avoid obj-c compatibility unless strictly needed and if there is no other way to achieve the same using pure swift. My understanding is that obj-c compatibility is important for you in this case, so yes, my solution wouldn't solve your specific case. – Antonio Aug 23 '14 at 11:47
  • By the way your specific problem may not have a natural solution in OOP. If in a superclass (or protocol) you define a property of a certain type A, in the subclass you cannot declare the same property using a type B inherited from A (or adopting the protocol), because B is different than A - whereas the opposite is almost true. – Antonio Aug 23 '14 at 11:59
  • Example: if `Car` is a subclass of `Drivable`, you cannot assign an instance of `Drivable` to a variable of type `Car`: `let car: Car = Drivable()` (here I am supposing that `Drivable` is a concrete type and not a protocol, but the same concept applies). You can do the opposite though: `let drivable: Drivable = Car()` – Antonio Aug 23 '14 at 12:00
  • @Antonio: But in this case it's a read-only property in `DriverType`. So nobody would be able to assign `Drivable()` to it. – newacct Aug 24 '14 at 20:08
  • @newacct: just make the prop writable in the protocol: `var vehicle: Drivable { get set }` – Antonio Aug 24 '14 at 20:13
  • @Antonio: But it isn't writable. If it were writable that would be another question. In Objective-C you can implement a read-only property with a subtype property in a subclass I believe. – newacct Aug 24 '14 at 20:27
  • @newacct not sure I caught what you mean - you want to make it readonly in the protocol, but then implement as writable? – Antonio Aug 24 '14 at 20:34
  • What I mean is, the literal translation of the code in the question to Objective-C, compiles and is safe: `@protocol Drivable @property (readonly) double speed; @end @protocol DriverType @property (readonly) id vehicle; @end @interface Car : NSObject @property (readwrite) double speed; @end @interface Driver : NSObject @property (readwrite) Car *vehicle; @end @implementation Car @end @implementation Driver @end` – newacct Aug 28 '14 at 10:39
  • @newacct in swift you can't do that straight away. By the way I consider that unnecessary because it breaks polymorphism. You can pass an object adopting the `DriverType` interface around and use its methods and properties, but if you pass a `DriverType` as if it were a `Driver`, sooner or later you'll have problems because a `Drivable` is not a `Car`, although `Car` can behave as a `Drivable`. Unless you have a hierarchy of class/protocols using these classes and protocols, I consider either `DriverType` or `Driver` unnecessary. However the answer is no, you can't do that in swift – Antonio Aug 28 '14 at 11:39