redbeard1970
redbeard1970

Reputation: 331

Multiple implementations of common interface

I am creating an interface say 'Car'

public interface Car {
    public void drive(int Speed); // for cars which do not have gears
}

which has multiple implementation like Bus, Truck and etc Then in a main class called Trafic, the drive method of all the implementaions should be called (the order does not matter). I have two option for Traffic Class design:

1) either use a property for every Car implementation which makes the consrtuctor hard to maintain and ugly (with many arguments of same type and the fact that by adding new Car implementation it should be changed) like this:

public class Traffic {
  private Car bus;
  private Car truck;
  ...
  public Traffic(Car bus, Car truck,...){
    this.bus = bus;
    this.truck = truck;
   ...
  }

  public void run(){
     bus.drive();
     truck.drive();
     ...
  }

}

2) or pass a list of Car's to Traffic. Then what if i want to check something else about the Bus, so sometimes i need to find an implementation with Instance Of. It would be something like this:

public class Traffic {
  private List<Car> cars;
    public Traffic(Car bus, Car truck,...){
    this.bus = bus;
    this.truck = truck;
   ...
  }
  public void run(){
    for(Car car : cars){
       car.drive();
     }
  }
}

I feel kind of unsatisfied with either of these solutions. Is there any other solution?

Upvotes: 0

Views: 2698

Answers (2)

Esko
Esko

Reputation: 29385

what if i want to check something else about the Bus

In polymorphic design you should create a functionality delegating method that is called on the higher level and put the implementation specific logic forking there. Sometimes this means creating a whole mini-DI helper class to provide extra services to the polymorphic implementations of parent class which can make things look a bit weird but at the same time there really isn't a way around it when in OOP.

Consider the following class hierarchy:

public abstract class Vehicle {
    public abstract void drive();
}

public abstract class Car extends Vehicle {
    public void drive() {
        System.out.println("Driving around...");
    }
    public void honkHorn() {...}
}

public final class Bus extends Car {
}

public final class Truck extends Car {
    @Override
    public void drive() {
        super.drive();
        honkHorn();
    }
}

Here I have augmented your class hierachy with two additional features:

  • There is now a common super class Vehicle because maybe you want to expand to boats which are also driven but definitely not in the same way as cars.
  • Truck honks its horn every time it is also driven

The latter part is the important one here; one of the bigger benefits of polymorphism is introducing extra functionality like this in subclasses. If external dependencies exist and state checking is required - for example we really don't want to drive our boats on highways - you can introduce a helper on top level to provide the implementations with extra details. So, changing Vehicle like this...

public abstract class Vehicle {
    public abstract void drive(Surface surface);
}

now allows us to define a Boat...

public abstract class Boat extends Vehicle {
    @Override
    public void drive(Surface surface) {
        if (surface.isWater()) {
            doDrive(surface);
        }
    }
    // this is needed to ensure that extending classes really do implement the driving capability!
    public abstract void doDrive(Surface surface);
}

which limits the applicability of all Boat implementations to only watery surfaces.

To bring all this together with your Traffic class you can now start to consider what is the proper object hierarchy for dealing with various aspects of the class modeling given here. You could for instance define that Traffic happens on Surface and the class handles only moving instances of Vehicles on Lanes and the logic would query for eg. vehicle dimensions and speed to consider such cases as when a bus can switch lanes after picking up passengers from a stop or if the lane is already filled to brim and is moving too slow to warrant a lane change at all.

To answer your question, internally you'll probably want to use a List<Vehicle> in any case as that provides ordering for your vehicles, but the Traffic class should not take in a list of vehicles as in case where Traffic represents a highway you really don't join the traffic from random spots but from specific intersections/junctions which insert the incoming vehicle to specific spot in relation to the highway. So, you probably want to create a few methods for inserting/removing Vehicles into and out from Traffic at specific points. In this scenario the internal list probably should contain wrapper objects to augment the vehicle info with the relative position of the vehicle on the lane to make inserting new vehicles easier and in general to update all the positions in one easy loop, but this post is now getting really long and I'm assuming a lot here... :)

Upvotes: 2

davidxxx
davidxxx

Reputation: 131546

You could use a Varargs as argument and you should use generics in your collection it is safer.

public class Traffic {
  private List<Car> cars;
...
  public Traffic(Car... cars){
    this.cars = Arrays.asList(cars);
  }
...
}

If you want to have a List which has not the limitations of the list returned by Arrays.asList() you could do that :

public class Traffic {
  private List<Car> cars;
...
  public Traffic(Car... cars){
    this.cars = new ArrayList<>();
    this.cars.addAll(Arrays.asList(cars));
  }
...
}

Upvotes: 2

Related Questions