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
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.
Using an enum (such as CarKind
) seemingly requires several messy switch statements:
speed
field of each carCar
objects from the Cars
classHow 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.
The AbstractCollection class in Java is a part of the Java Collection Framework and implements the Collection interface. It is used to implement an unmodifiable collection, for which one needs to only extend this AbstractCollection Class and implement only the iterator and the size methods.
An abstract class is a class that is declared abstract —it may or may not include abstract methods. Abstract classes cannot be instantiated, but they can be subclassed.
An interface is a collection of abstract methods, it does not have any concrete methods, unlike an abstract class. But unlike abstract class, an interface provides full abstraction in Java. It can have both methods and variables just like a class.
Declaring a class as abstract means that it cannot be directly instantiated, which means that an object cannot be created from it. That protects the code from being used incorrectly. Abstract classes require subclasses to further define attributes necessary for individual instantiation.
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).
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With