Home > Software engineering >  Generic class of generic class with one argument/parameter
Generic class of generic class with one argument/parameter

Time:01-13

I have this syntactical issue with my data structure. What makes it worse is that I can not talk about it in detail. The company I am employed at operates in the public transportation sector and I am under NDA and boss would kill me if I posted anything too specific. I hope you understand!

I have this perfect example though. There are no inconsistencies at all ;) Well, okay, there are. However I am convinced that most of you out there are smart enough to get what is of importance here.

Basic structure:

class Propulsion {
    var horsePower: Double
    init(horsePower: Double) {
        self.horsePower = horsePower
    }
    static let pedes = Propulsion(horsePower: 0.2)
}

class Motor: Propulsion {
    var range: Double
    init(range: Double, horsePower: Double) {
        self.range = range
        super.init(horsePower: horsePower)
    }
    static let otto = Motor(range: 1000, horsePower: 100)
    static let electric = Motor(range: 400, horsePower: 200)
}

class Vehicle<P: Propulsion> {
    var propulsion: P
    init(propulsion: P) {
        self.propulsion = propulsion
    }
}

class Bicycle<P: Propulsion>: Vehicle<P> {
    var hasFrontSuspension: Bool
    init(hasFrontSuspension: Bool, propulsion: P) {
        self.hasFrontSuspension = hasFrontSuspension
        super.init(propulsion: propulsion)
    }
}

class Car<P: Propulsion>: Vehicle<P> {
    func rangePerHorsePower() -> Double where P: Motor {
        propulsion.range / propulsion.horsePower
    }
}

Now I would like to declare a parking spot for a car. Like so:

var carParkingSpot: ParkingSpot<Car<Motor>>

For the class ParkingSpot I have some class like this in mind:

class ParkingSpot<V: Vehicle<P>> where P: Propulsion {
    var vehicle: Vehicle<P>
    init(vehicle: Vehicle<P>) {
        self.vehicle = vehicle
    }
    func taxForRange() -> Double where P: Motor {
        vehicle.propulsion.range * 50
    }
}

From the last bit I get back a bunch of

Cannot find type 'P' in scope

This one doesn’t work either:

class ParkingSpot<V: Vehicle<P: Propulsion>>

Expected '>' to complete generic argument list

This implementation works though:

class ParkingSpot<V: Vehicle<P>, P: Propulsion> {
    var vehicle: Vehicle<P>
    init(vehicle: Vehicle<P>) {
        self.vehicle = vehicle
    }
    func taxForRange() -> Double where P: Motor {
        vehicle.propulsion.range * 50
    }
}

However I don’t want to duplicate the Motor bit:

var carParkingSpot: ParkingSpot<Car<Motor>, Motor>

How can I accomplish this with just one generic parameter?

CodePudding user response:

This seems to work:

class ParkingSpot<V: Vehicle<Propulsion>>
{
    var vehicle: V
    init(vehicle: V)
    {
        self.vehicle = vehicle
    }

    func taxForEngineeNoise() -> Double
    {
        switch vehicle.propulsion
        {
        case is Motor:
            return vehicle.propulsion.horsePower * 50

        default:
            ...
        }
    }

    func taxForRange() -> Double
    {
        if let motor = vehicle.propulsion as? Motor
        {
            return motor.range * 50
        }
        else
        {
            ...
        }
    }
}

Alternatively, perhaps hide the duplication where you can?

typealias ParkingSpotX = ParkingSpot<Car<Motor>, Motor>

var parkingSpot: ParkingSpotX

CodePudding user response:

You may use the "Protocol oriented" approach:

protocol PropulsionP {
    var horsePower: Double { get }
}

protocol MotorP: PropulsionP {
    var range: Double { get }
}

struct MotorS: MotorP {
    var range: Double
    var horsePower: Double

    init(range: Double, horsePower: Double) {
        self.range = range
        self.horsePower = horsePower
    }
}

protocol VehicleP {
    associatedtype P: PropulsionP

    var propulsion: P { get }
}

struct BicycleS<Prop: PropulsionP>: VehicleP {
    let hasFrontSuspension: Bool
    var propulsion: Prop

    init(
        hasFrontSuspension: Bool,
        propulsion: Prop
    ) {
        self.hasFrontSuspension = hasFrontSuspension
        self.propulsion = propulsion
    }
}

struct CarS<Prop: PropulsionP>: VehicleP {
    var propulsion: Prop

    func rangePerHorsePower() -> Double where P: MotorP {
        propulsion.range / propulsion.horsePower
    }
}

struct ParkingSpotS<V: VehicleP> {
    var vehicle: V

    init(vehicle: V) {
        self.vehicle = vehicle
    }

    func taxForRange() -> Double where V.P: MotorP {
        vehicle.propulsion.range * 50
    }
}

var carParkingSpot: ParkingSpotS<CarS<MotorS>>

No double MotorS bit. Quod erat demonstrandum.

I used the somewhat unusual naming to emphasize the point.

(needed to make some edit, erronously typed Motor where I actually need MotorP)

Update

I was on the road with my preferred car and tried it out:

var carParkingSpot: ParkingSpotS<CarS<MotorS>> = .init(
    vehicle: .init(
        propulsion: .init(
            range: 760,
            horsePower: 240
        )
    )
)

print(carParkingSpot.taxForRange())
38000.0

Alternatively you can use this initialiser:

var carParkingSpot: ParkingSpotS = .init(
    vehicle: CarS(
        propulsion: MotorS(
            range: 760,
            horsePower: 240
        )
    )
)
  • Related