An example of my problem is shown below. Both classes Car
and Bike
have the same fields (wheel
and bodyColor
).
In class AssembleVehicle
, the method getDetails
is almost duplicated for Car
and Bike
. Is there any way to remove redundancy or improve coding standards?
class Vehicle {
Car car;
Bike bike;
//setter & getter
}
class Car {
String wheel;
String bodyColor;
//setter & getter
}
class Bike {
String wheel;
String bodyColor;
//setter & getter
}
class AssembleVehicle {
public void init(Vehicle v) {
getDetails(v.getCar);
getDetails(v.getBike);
}
private void getDetails(Car c) {
String wheel = c.wheel;
String bodyColor = c.bodyColor;
}
private void getDetails(Bike c) {
String wheel = c.wheel;
String bodyColor = c.bodyColor;
}
}
Here, how we can remove code redundancy for getDetails
?. Can we use this method only once?
What I understand about generics is: generics allow you to customize a "generic" method or class to whatever type you're working with. For example, suppose you have a method that adds two numbers together. In order to work with the types themselves, you might have to create multiple versions of this method.
CodePudding user response:
Thinking about OOP in this way places the wrong emphasis. Even if you fix your initial mishap of having a Vehicle with a car and a bike inside of it (seriously?), the 'improved' model of the Vehicle superclass with the wheel and bodyColor fields is probably broken (depending on the actual domain you are working in). For instance a sleigh is a vehicle without wheels.
The problem is that we are tempted to assume that if two examples of a category share some common traits, that these traits are present for all the members of this category (BTW this tendency to generalize might also be the cause of many problems in society).
The power of OOP does not come from extracting super classes like the Vehicle and 're-using' some fields. Direct subclassing is a very drastic measure that should not be applied lightly, because it means all present and future subclasses MUST inherit the complete contract (data and behavior) of the super class. As soon as you want to add a new subclass and are forced to make exceptions you know the model is wrong. And changing it will impact all the existing sub classes.
A much better tool is to extract interfaces, to encapsulate certain aspects of a category of classes:
interface Wheeled {
String getWheels();
}
class Car implements Wheeled {
private String wheels;
public String getWheels() {
return wheels;
}
}
class Bike implements Wheeled {
private String wheels;
public String getWheels() {
return wheels;
}
}
This would allow treating Cars and Bikes the same, if you are only interested in wheels:
List<Wheeled> wheeledVehicles = new ArrayList<>();
wheeledVehicles.add(new Car());
wheeledVehicles.add(new Bike());
wheeledVehicles.get(0).getWheels());
If you are interested in body color, or even wheels AND body color, you can play with the interfaces:
interface Wheeled {
String getWheels();
}
interface Coloured{
String getBodyColour();
}
interface WheeledAndColoured extends Wheeled, Coloured {}
Which allows:
List<WheeledAndColoured> wheeledAndColouredVehicles = new ArrayList<>();
wheeledAndColouredVehicles.add(new Car());
wheeledAndColouredVehicles.add(new Bike());
WheeledAndColoured first = wheeledAndColouredVehicles.get(0);
first.getWheels());
first.getBodyColour());
If you really want to, you can extract the wheels field into a super class, but the immediate gain is limited. Private fields are an implementation detail, and pulling them into a super class is certainly not the type of re-use that will make a big difference. At the same it time, it would create a very strong dependency between those classes and make future changes harder.
Maybe if you need to implement some 'complex' logic related to wheels, it is time to create the Wheels class:
class Wheels {
private String type;
private int number;
private double price; // don't use double for price in real code
public double getReplacementCosts() {
return ((double) number) * price;
}
}
Now the Car class can collaborate with the Wheels class as a field:
class Car implements Wheeled {
private Wheels wheels;
public String getWheels() {
return wheels.getType();
}
public double getMaintenanceCosts() {
return wheels.getReplacementCosts();
}
}
Notice that, because we didn't tie in Car and Bike with a common super class, we are not forced to change either the Wheeled interface, nor the Bike class. If you want to add the Wheels logic to Bike then you can easily do so, but you are not forced to. You would be, if the wheels fields was in the shared super class Vehicle.
So the motto is: Favor collaboration over extension because it's way more flexible.
CodePudding user response:
You should use polymorphism and abstract classes to enhance code clarity. By the way I am not sure about the name of the class Vehicle
which acts like a vehicle container (maybe a name like Garage
would be more appropriate).
class Garage {
Car car = new Car();
Bike bike = new Bike();
//setter & getter
}
abstract class Vehicle {
String wheel;
String bodyColor;
//setter & getter
abstract void getDetails();
}
class Car extends Vehicle {
void getDetails() {
System.out.println("Car: wheel=" wheel ", bodyColor=" bodyColor);
}
}
class Bike extends Vehicle {
void getDetails() {
System.out.println("Bike: wheel=" wheel ", bodyColor=" bodyColor);
}
}
class AssembleVehicle {
public void init(Garage g) {
g.getCar().getDetails();
g.getBike().getDetails();
}
}
CodePudding user response:
I agree with other commenters that the Vehicle class does not really make sense. I recommend following their advice and having Car and Bike extend the Vehicle class. This is better than using generics, because you can "guarantee" that you cannot use AssembleVehicle with classes that aren't vehicles and don't meet the requirements of having a wheel and a bodyColour.
However, if you still want to use generics, here is how you can use them:
class Car {
String wheel;
String bodyColor;
//setter & getter
}
class Bike {
String wheel;
String bodyColor;
//setter & getter
}
class AssembleVehicle <T> {
public void init(T vehicle) {
getDetails(vehicle);
}
private void getDetails(T vehicle) {
String wheel = vehicle.wheel;
String bodyColor = vehicle.bodyColor;
}
}
This allows you to write the getDetails method only once.