3

I have two classes that know each other in a many-to-many relationship. Now I want to have two additional classes that inherit from the existing classes. The association between the inherited classes should stay.


Example:

Say you have a Vehicle

public class Vehicle{ }

And you have Tires

public class Tire { }

Now you want the Vehicle to have many Tires

public class Vehicle
{
    public Tire[] Tires { get; set; }
}

That's great! But a Trucks Tires are significantly different than other Vehicles.. They're larger, have spikes (big metal Ramones studs!), are made of space age polymers (plausibly from outer-space) and filled with novel gases (Isn't Neon cool?).

public class TruckTire : Tire { }

But here's the rub... my Truck now only has normal Tires if it inherits from Vehicle...

public class Truck : Vehicle { }

Crux of the problem & question:

So... How do I make it so when people access Truck.Tires they get back something that's cast as TruckTire[] instead of just regular old Tire[] when the Tires property/field is defined in the base class with a type of Tire[] not TruckTire[]? Yes the instances at runtime could be TruckTires, but that would require a cast to utilize as such...

  • 1
    I would strongly suggestion you read Eric Lippert's series of article Wizard and Warrior. It a great explanation on the limit of the type system when enforcing those kind of rules :) – Laurent Bourgault-Roy May 19 '15 at 14:16
4

TruckTire is a Tire all right so inheritance is appropriate here. Truck however cannot use Tires, only TruckTires. This we can achieve by introducing some elementary generics. If the program would need additional types, say Tractor and TractorTire, the solution could easily be extended to this.

public class Vehicle<T extends Tire> {

    private T[] tires;

    public T[] getTires() {
        return tires;
    }

    public void setTires(T[] tires) {
        this.tires = tires;
    }

    @Override
    public String toString() {
        if (tires == null || tires.length <= 0) {
            return String.format("%s has no tires", this.getClass().getSimpleName());
        } else {
            return String.format("%s has %d tires of type %s", this.getClass().getSimpleName(),
                    tires.length, tires[0].toString());
        }
    }
}

public class Truck<T extends TruckTire> extends Vehicle<T> {
}

public class Tire {

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

public class TruckTire extends Tire {
}

public class App {

    public static void main(String[] args) {
        // vehicle
        Vehicle<Tire> vehicle = new Vehicle<>();
        System.out.println(vehicle);
        Tire[] tires = {new Tire(), new Tire(), new Tire(), new Tire()};
        vehicle.setTires(tires);
        System.out.println(vehicle);

        // truck
        Truck<TruckTire> truck = new Truck<>();
        System.out.println(truck);
        TruckTire[] truckTires = {new TruckTire(), new TruckTire(), new TruckTire(), new TruckTire()};
        truck.setTires(truckTires);
        System.out.println(truck);
    }
}

Console ouput:

Vehicle has no tires
Vehicle has 4 tires of type Tire
Truck has no tires
Truck has 4 tires of type TruckTire
  • Please note, the question has been significantly edited from its original. You may wish to revise your answer. – user40980 May 18 '15 at 18:19
  • I have updated my answer. – iluwatar May 18 '15 at 19:55
  • 1
    Could you go a bit into describing your solution? The classic statement is "the whiteboard doesn't have a compiler." Explaining how the solution works on Programmers.SE is often seen as much more important than any associated code. – user40980 May 18 '15 at 19:57
  • Ok, I've added some explanation. Hope this helps. – iluwatar May 18 '15 at 20:15
2

Based on your update from 2015-05-18.

TruckTire should be a private subclass of Tire. Tire should contain the interface other client classes (Truck, Car) use, and TruckTire encapsulates the behavior. The Truck's interface to TruckTire is the same as Car's interface is to CarTire. They all know them as Tires. Use the Strategy pattern for Tire.

  • @Marc I can point out a concrete LSP violation here: SuperParent has a method addChild(SuperChild) that accepts anything that inherits from or implements SuperChild, including SuperChild itself. However, SubParent also has a method of the same name, but restricts the argument to be SubChild. This means passing a SuperChild to SubParent.addChild($) is an error. This is an LSP violation unless these two methods are independent of each other (i.e. the SubParent method must hide the SuperParent method). – rwong May 16 '15 at 22:56
  • Please note, the question has been significantly edited from its original. You may wish to revise your answer. – user40980 May 18 '15 at 18:19
  • Also note that every post on Stack Exchange has a detailed edit history that anyone can view for historical purposes, so leaving irrelevant material around for the Google bots to index seems... pointless. – Robert Harvey May 18 '15 at 22:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.