Stack Overflow Asked by Haifeng Zhang on January 3, 2022
I had an interview yesterday and had asked an OOD question:
Race-Car Store System:
The system stores information about cars available for players.
- Two types of gear changing strategies: manual/automatic.
- Two types of fuel: gasoline/diesel.
Design a system that can produce cars requested by players (If the
player wants a car with manual gear changing and burn diesel, your
system should provide one instance of the car meets the requirements).
The system should have good scalability and maintainability.
My thoughts and solution:
My thought is the requirement contains two attributes: gear and fuel. I plan to make an abstract class contains the attributes and corresponding behaviors. Considering scalability would have an interface Movable
which contains behaviors what a car can do.
If any new attribute is added in the future, either create a new abstract class contains the new attribute or add the attribute into the existing abstract class, if there’s new behaviors are required, I would either create new interface or add the behavior into existing interface.
Here’s what I have done:
An interface contains general behaviors, currently has showSpecs()
only.
public interface Movable {
public String showSpecs();
}
An abstract class contains attributes fuel
and gear
public abstract class Car implements Movable {
String gear;
String fuel;
abstract void setFuel(String fuel);
abstract String getFuel();
abstract void setGear(String gear);
abstract String getGear();
}
Now the race car class implementaion:
public class RaceCar extends Car {
public RaceCar(String fuel, String gear) {
this.fuel = fuel;
this.gear = gear;
}
public void setFuel(String fuel) {
this.fuel = fuel;
}
public String getFuel() {
return this.fuel;
}
public void setGear(String gear) {
this.gear = gear;
}
public String getGear() {
return this.gear;
}
public String showSpecs() {
StringBuilder sb = new StringBuilder();
sb.append("Gear:").append(this.gear);
sb.append("Fuel:").append(this.fuel);
return sb.toString();
}
}
Below is the main class I have:
public class Main {
public static void main(String[] args) {
System.out.println("get started...");
Car car = new RaceCar("diseal", "automatic");
System.out.println(car.showSpecs());
}
}
The interviewer replied that the solution I provided is not scalable and hard to maintain but didn’t provide details so I am still confused about what mistakes I made and how to improve it.
Can anyone help share your thoughts and point out what am I supposed to improve?
Thanks!
I thought that maybe he was expecting something like pluggable classes when he mentioned scalable and maintainable. So I think maybe this strategy pattern was expected. If transmission or injection is expected to do some real logic, I can assume them as behaviors instead of just state. Thus results with this implementation.
public interface TransmissionPolicy {
public void transmit();
}
public class AutomaticTransmission implements TransmissionPolicy {
public void transmit() {
//do some real logic here
print("automatic...");
}
}
public class ManualTransmission implements TransmissionPolicy {
public void transmit() {
print("we love it..."); //just an example of really simple logic
}
}
public interface InjectionPolicy {
public void inject();
}
public class DieselInjection implements InjectionPolicy {
public void inject() {
print("diesel");
}
}
public class GasolineInjection implements InjectionPolicy {
public void inject() {
print("gasoline...");
}
}
public class Car {
public void make(TransmissionPolicy transmission, InjectionPolicy injection) {
//set other parts
transmission.transmit();
//set other parts
injection.inject();
//other parts
}
}
//--------------somewhere in some clients client --------------------
Car car = new Car();
//actually, to be really configurable use a factory method here.
car.make(new ManualTransmission(), new GasolineInjection());
If that was expected then with just lambdas or command pattern it would be made also.
Answered by Taha Yavuz Bodur on January 3, 2022
I would have answered this question with 2 classes, Car
and CarBuilder
:
public final class Car {
private final Fuel fuel;
private final Gears gears;
public Car(Fuel fuel, Gears gears) {
this.fuel = fuel;
this.gears = gears;
}
public Fuel getFuel() {
return fuel;
}
public Gears getGears() {
return gears;
}
enum Fuel {
GASOLINE,
DEISEL
}
enum Gears {
AUTOMATIC,
MANUAL
}
}
public class CarBuilder {
//sensible defaults:
private Car.Fuel fuel = Car.Fuel.GASOLINE;
private Car.Gears gears = Car.Gears.MANUAL;
public CarBuilder() {
}
public CarBuilder withFuelType(Car.Fuel fuel) {
this.fuel = fuel;
return this;
}
public CarBuilder withGearBox(Car.Gears gears) {
this.gears = gears;
return this;
}
public Car build() {
return new Car(this.fuel, this.gears);
}
}
Scalability and maintainability is achieved by the fact that these are the only 2 classes that need to be changed in the future, should requirements change. Car
is immutable and also contains the enums required to represent its internal state, so these attributes can't leak out of the context/object in which they make sense so make it easier to maintain in the future.
The builder class, while basic in its current form, can be extended to accommodate more complex construction requirements without leaking implementation details into the Car
class.
The default values are optional, but might make sense.
A car can be constructed as such:
//Default car:
Car car = new CarBuilder().build();
//Customised car:
Car car = new CarBuilder().withFuelType(Car.Fuel.DEISEL).withGearBox(Car.Gears.AUTOMATIC).build();
Answered by StuPointerException on January 3, 2022
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP