8

The Scenario

I'm making a program in Java that involves cars.

NOTE: I've simplified this scenario (to the best of my ability) to make it both more general and easier to understand. I'm not actually working with cars.

I've created a Cars class, which is a collection of Car objects.

The Car object has a speed (double) and a year (int). The constructor takes the year as a parameter, for example:

public class Car {
    private int year;
    private double speed;

    public Car(int year) {
        this.year = year;
    }
}

Here's the tricky part... A car must have a kind (let's say Corvette or Clunker). A Corvette will have a speed of 0.9 and a Clunker will have a speed of 0.1. A Car can never be instantiated without specifying what kind of car it should be. So now, to create a car, we have:

Car car = new Car(1998, Corvette);

The Corvette we've just created will be a Car object with a speed of 0.9.

The Problem

My actual situation involves many more kinds of cars, and each car has several specific attributes besides speed (maybe there are also fields for color, numDoors and fuelTankSize). With so many kinds of cars (each with their own specific attributes), the code is becoming more complex than I'd like.

Possible Solutions

  1. I could work with sub classes, that is, have an abstract Car class that's extended by Corvette and Clunker, but then I have the problem of using a Cars object (because I can't make a collection of something that can't be instantiated). See EDIT below.

  2. Using an enum (such as CarKind) seemingly requires several messy switch statements:

    • to populate the speed field of each car
    • to create Car objects from the Cars class
    • etc.

How You Can Help

I'm looking for a solution that allows a single Cars class to contain every Car object. I don't want different collections (like Corvettes, Clunkers). I'm also looking for a solution that allows the creation of Car objects based on the attributes of an individual car kind... as previously mentioned, creating a new Car of kind Corvette would result in a speed of 0.9. There should be no other way to specify a car's speed.

Is there a best practice in this situation? Have I made the example clear enough?

Thanks.

EDIT: The reason I don't want a collection of abstract Car objects is because the point of the Cars collection is to create and manipulate Car objects, regardless of their kinds. Car being abstract seems to complicate this. If you think this is the best solution, please answer accordingly.

Peter
  • 4,021
  • 5
  • 37
  • 58
  • 2
    What do you mean by "I can't make a collection of something that can't be instantiated"? Yes, you can. – NullUserException Sep 21 '11 at 00:55
  • Sorry, I've tried to clarify in an EDIT above. – Peter Sep 21 '11 at 01:02
  • Why would you need a switch statement? `Car car1 = new Corvette(...); Car car2 = new Clunker(...);` would work just fine. – NullUserException Sep 21 '11 at 01:04
  • 1
    I echo @NullUserExceptionఠ_ఠ's sentiments. It is unclear why you can't simply have a collection of `Car`s. Are you concerned that when you retrieve a `Car` element from such a selection, there is some action you will not be able to be performed on it? Are you aware that you could add `Corvette` to a `List` collection, without doing anything fancy? – cheeken Sep 21 '11 at 01:09
  • Well the complexity arises when creating 100 Corvettes, 200 Clunkers, etc. This would require loops for each kind of car, and each loop would have to create some subclass of `Car` (which would, of course, have to invoke methods and populate fields in `Car`). Notwithstanding, one of the answers mentions that I "have to say it someplace." I'm just trying to say it in the *best* place and keep the code as DRY as possible. Maybe the abstract class with subclasses for each car kind is the way to go, after all? – Peter Sep 21 '11 at 01:19
  • @Peter have you read my answer? I'm pretty confident it can help you with your scenario, if not let me know how it doesn't fit. :) – Daryl Teo Sep 21 '11 at 01:54
  • is there a common set of attributes for *all* cars? and then things like a 4 wheel drive for an suv or a sun roof for a van? – Ray Tayek Sep 21 '11 at 03:06
  • also, why avoid inheritance? seems like there would be sports cars, sedans, suvs, trucks etc. ? and these would have some common special attributes? – Ray Tayek Sep 21 '11 at 03:07

4 Answers4

11

I'm looking for a solution that allows a single Cars class to contain every Car object. I don't want different collections (like Corvettes, Clunkers). I'm also looking for a solution that allows the creation of Car objects based on the attributes of an individual car kind... as previously mentioned, creating a new Car of kind Corvette would result in a speed of 0.9. There should be no other way to specify a car's speed.

Oh boy oh boy there are so many ways to deal with this that we could go on all day! I will do a brain dump, and hopefully it will not be too much for you to deal with.


solution 1: use Strategy.

A strategy is basically a way to separate heavy substitutable logic from another class. In this case, every car needs to be created differently. A strategy is PERFECT for this.

Sorry if I mix in some C# by accident... been a long time since I javaed.

public interface CarCreationStrategy{
   void BuildCar(Car theCar);
}

public class CorvetteStrategy implements CarCreationStrategy{
   public void BuildCar(Car theCar){
      theCar.Type = "Corvette";
      theCar.Speed = 0.9;
      theCar.Comments = "Speedster!";
   }
}

public class ToyotaStrategy implements CarCreationStrategy{
   public void BuildCar(Car theCar){
      theCar.Type = "Toyota";
      theCar.Speed = "0.5";
      theCar.Comments = "Never dies, even if you drop it from the top of a building";
   }
}

Now, you can pass a strategy in with your car constructor.

public class Car{
   // Variables ...

   public Car(CarCreationStrategy strategy, int year){
      strategy.BuildCar(this); // Implements your properties here.
      this.year = year;
   }
}

So, what you get now is so awesome!

List<Car> cars = new List<Car>();
cars.Add(new Car(new CorvetteStrategy(),1999));
cars.Add(new Car(new ToyotaStrategy(),2011);

And this will do exactly what you want.

However, you get a coupling between the strategy and the Car.


solution 2: use Factory.

Factory is an okay solution for this as well, and is probably easier. What you do is have a CarFactory, with multiple factory methods for creating each type of car.

public class CarFactory{
   public static Car BuildCorvette(int year){
      Car car = new Car(year);
      car.Type = "Corvette;
      car.Speed = 0.9";
      return car;
   }

   public static Car BuildToyota(int year){
      Car car = new Car(year);
      car.Type = "Toyota;
      car.Speed = 0.5";
      return car;
   }
}

Usage:

List<Car> cars = new List<Car>();
cars.Add(CarFactory.BuildCorvette(1999));
cars.Add(CarFactory.BuildToyota(2011));

So the good thing about this is you don't have to worry about instantiating Car now. its all handled by CarFactory, decoupling your "instantiation logic" from your code. However, you still need to know which car you want to build and call that method accordingly, which is still a small coupling.


solution 3: strategy factory!

So, if we wanted to get rid of that last bit of couplings, lets combine the two together!

public class CarFactory{
   public static Car BuildCar(CarCreationStrategy strategy, int year){
      Car car = new Car(year);
      strategy.BuildCar(car);
      return car;
   }
}

List<Car> cars = new List<Car>();
cars.Add(CarFactory.BuildCar(new CorvetteStrategy(),1999));
cars.Add(CarFactory.BuildCar(new ToyotaStrategy(),2011);

Now you have a Strategy for building cars, a Factory that builds them for you, and a Car with no extra couplings from your original. Wonderful, isn't it?

If you have worked with Swing, you will notice that this is how they handle a few things like the Layouts (GridBagLayout, GridLayout are all strategies). There's also a BorderFactory as well.


Improvement

Abstract Strategy

public interface CarCreationStrategy{
   void BuildCar(Car theCar);
}

public class AbstractStrategy:CarCreationStrategy{
   public string Type;
   public double Speed;
   public string Comments;

   public void BuildCar(Car theCar){
       theCar.Type = this.Type;
       theCar.Speed = this.Speed;
       theCar.Comments = this.Comments;
   }
}

public class CorvetteStrategy extends AbstractStrategy{
   public CorvetteStrategy(){
      this.Type = "Corvette";
      this.Speed = 0.9;
      this.Comments = "Speedster!";
   }
}

public class ToyotaStrategy extends AbstractStrategy{
   public ToyotaStrategy{
      this.Type = "Toyota";
      this.Speed = "0.5";
      this.Comments = "Never dies, even if you drop it from the top of a building";
   }
}

Using this gives you the flexibility to create AbstractStrategies on the fly (say, pulling car properties from a data store).

Daryl Teo
  • 5,394
  • 1
  • 31
  • 37
  • 1
    +1 for a very informative answer. Factory seems to be most like what I need. Could you further explain the advantages of combining strategy and factory? – Peter Sep 21 '11 at 02:23
  • A possible use case: Say you want to let a user select a car to build. If you stuck with Factory, you'd still need to have your "switch" statement somewhere to map the input between the user selection and which method to call (if "corvette" then BuildCorvette()) . Using a strategy lets you map user selection to a build strategy with Map – Daryl Teo Sep 21 '11 at 02:28
  • Actually, I just thought of an improvement while typing that. Gimme sec. – Daryl Teo Sep 21 '11 at 02:30
  • Done added a new segment at the bottom. I get a little carried away :) – Daryl Teo Sep 21 '11 at 02:36
  • I ended up using a strategy, but instead of using separate classes to implement `CarCreationStrategy`, I used an enum (`CarTypes`). Within each enum value, I defined `buildCar`. To create a Corvette, I write: `Car car = new Car(1998, CarTypes.CORVETTE);` where `CarTypes.CORVETTE` is the appropriate enum value. Within the car class, all I have to to is call `type.buildCar(this)`. Now the kinds of car are clearly defined in the enum, and the strategy pattern allows me to apply their attributes very elegantly. A win-win! – Peter Sep 21 '11 at 02:58
  • See my final result in my answer. – Peter Sep 21 '11 at 03:15
3

I used the method described in Daryl Teo's answer, but I modified it a little bit to include an enum (since there are a finite number of individual cars). Because his answer was so instrumental to the development of my final solution, I selected it as the best answer.

The CarCreationStrategy interface:

public interface CarCreationStrategy {
    public void buildCar(Car car);
}

enum CarTypes implements CarCreationStrategy{
    CORVETTE() {
        @Override
        public String toString() {
            return "Corvette";
        }

        public void buildCar(Car car) {
            car.setSpeed (0.9);
        }
    },
    CLUNKER() {
        @Override
        public String toString() {
            return "A piece of junk!";
        }

        public void buildCar(Car car) {
            car.setSpeed (0.1);
        }
    }
}

The Cars class:

public class Cars {
    private List<Car> cars = new List<Car>;

    public void createCar() {
        // There would be logic here to determine what kind
        // We'll make a Corvette just to demonstrate
        Car car = new Car(1998, CarTypes.CORVETTE);
        cars.add(car);
    }
}

The Car class:

public class Car {
    private int year;
    private double speed;

    public Car(int year, CarType type) {
        this.year = year;
        type.buildCar(this);  // Sets the speed based on CarType
    }

    public void setSpeed(double speed) {
        this.speed = speed;
    }
}
Community
  • 1
  • 1
Peter
  • 4,021
  • 5
  • 37
  • 58
  • 1
    I didn't even know you could do Enums like that. Kudos! (Enums in C# have to be integral types) – Daryl Teo Sep 21 '11 at 03:28
  • 1
    I didn't know it either, until today. Turns out you can use all kinds of fancy tricks: http://download.oracle.com/javase/1,5.0/docs/guide/language/enums.html – Peter Sep 21 '11 at 03:46
2

You have to say it someplace - there's no getting around that. If you have many more kinds of Cars, you'll have to specify those attributes somewhere.

I don't see where subclassing helps you much, unless you can identify families of cars that are similar (e.g. sports cars, mini-vans, etc.).

One way to centralize all those attributes in one place is to instantiate Car using a factory method (it has a nice symmetry with manufacturing of physical cars, too).

Make the Car constructor private; have an enumeration of CarType that is the key into a Map of attributes for each type; pass in the CarType and get back a Car, fully initialized.

public class Car {

   private static final Map<CarType, Map<String, Object>> DEFAULT_ATTRIBUTES;

   private Map<String, Object> attributes; 

   static {
      DEFAULT_ATTRIBUTES = new HashMap<CarType, Map<String, Object>>();
      // Insert values here
   }

   private Car(Map<String, Object> initializers) {  
      this.attributes = new HashMap<String, Object>(initializers);
   }

   public static Car create(CarType type) {
      return new Car(DEFAULT_ATTRIBUTES.get(type));
   }
}
duffymo
  • 305,152
  • 44
  • 369
  • 561
  • I think I understand, but I'm not certain. Could you include Corvettes and Clunkers in your example, so I can see how the car's speed is assigned? Thank you. – Peter Sep 21 '11 at 01:09
0

I could work with sub classes, that is, have an abstract Car class that's extended by Corvette and Clunker, but then I have the problem of using a Cars object (because I can't make a collection of something that can't be instantiated).

It depends on how you use and how you create your Car object, but anyway, creating your object should always been easy, I'd imaging using a factory will do.

You will need to extend your Car object to have fields you wanted in general, and in concrete class, you can assign them individually, so instead of

Car car = new Car(1998, Corvette);

you do

Car car = new Corvette(1998);

and in your Corvette class:

public Corvette(int year)
{
    speed = 0.9;
}

But keep in mind always make sure Car is a Corvette, and Corvette is a Car.

Yuan
  • 2,690
  • 4
  • 26
  • 38