面向对象设计:可扩展和易维护的汽车商店系统。

3

我昨天参加了一次面试,并被问到一个OOD问题:

赛车店系统:

该系统存储关于玩家可用汽车的信息。

  • 有两种换挡策略:手动/自动。
  • 有两种燃料类型:汽油/柴油。

设计一个系统,可以为玩家提供请求的汽车(如果玩家想要一辆手动换挡并且燃烧柴油的汽车,则你的系统应该提供符合要求的汽车实例)。该系统应具有良好的可扩展性和可维护性。

我的想法和解决方案:

我的想法是需求包含两个属性:换挡和燃料。我打算创建一个包含这些属性及相应行为的抽象类。考虑到可扩展性,我会创建一个包含汽车行为的接口Movable

如果将来添加任何新属性,则可以创建一个包含新属性的新抽象类或将该属性添加到现有抽象类中,如果需要新的行为,则可以创建新接口或将该行为添加到现有接口中。

这是我的解决方案:

一个接口包含常规行为,当前只有 showSpecs()

public interface Movable {
    public String showSpecs();
}

抽象类包含属性 fuelgear

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();
}

现在是关于赛车类的实现:

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();
    }
}

以下是我拥有的主类:

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());
    }
}

面试官回答说,我提供的解决方案不可扩展且难以维护,但没有提供详细信息,因此我仍然对我的错误和如何改进感到困惑。有人能帮忙分享您的想法,并指出我应该改进什么吗?谢谢!

我可能会向他追问一个稍微更明确的需求,而不仅仅是“好”。 - Andy Turner
4
相对于需求,你已经过于复杂化了这个问题,因为似乎没有需要 Movable 或者 Car 的必要:只需要定义一个 RaceCar 类,给它两个字段并赋予构造函数。为燃料和车辆字段的值定义枚举类型即可。但是关于可扩展性——我很难理解实际上需要什么:每次想要一个实例时都调用构造函数;仅仅调用构造函数似乎已经足够而且是必要的。 - Andy Turner
1
我不喜欢你使用普通的“String”作为属性。最好使用枚举(或其他东西,这取决于这些实例实际上如何使用)。我也不太喜欢这些要求,因为它们并没有详细说明这些实例的使用方式。这使得设计出非常通用的东西变得困难。也许他们期望使用工厂模式或策略模式。谁知道呢.. - akuzminykh
@AndyTurner 感谢您的回复。我已经询问了需要什么样的可扩展性,并得到了“添加水箱”的答案。由于其中一位面试官看到了源代码,我没有提及细节,他说这不是他预期的答案。 - Haifeng Zhang
这个问题对我来说有些歧义,我问了几个相关的问题,但没有得到明确的答复。我认为这可能是因为“可扩展性”和“可维护性”对我来说太过宽泛了。 - Haifeng Zhang
@akuzminkyh 谢谢您的回复。我同意枚举看起来更好,感谢! - Haifeng Zhang
2个回答

1
我认为当他提到可扩展和可维护时,可能期望的是像可插拔类一样的东西。因此,我认为可能期望使用策略模式。如果期望传输或注入执行一些真正的逻辑,我可以将它们视为行为而不仅仅是状态。因此,结果就是这种实现方式。
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());


如果这是预期的话,那么只需要使用Lambda表达式或命令模式也可以实现。

感谢Taha的回答,点赞! - Haifeng Zhang
这里大多数类型都没有任何意义。TransmissionPolicy 没有逻辑!它在需求中没有被指定。 - Above The Gods

1

我会使用两个类 CarCarBuilder 来回答这个问题:

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);
   }
}

可扩展性和可维护性是通过只需改变这两个类来满足未来需求变化的事实而实现的。Car是不可变的,还包含枚举类型,用于表示其内部状态,因此这些属性无法泄漏到它们有意义的上下文/对象之外,使其更容易在将来维护。
建造者类在当前形式下很基本,但可以扩展以适应更复杂的构建要求,而不会泄漏实现细节到Car类中。
默认值是可选的,但可能是有意义的。
车辆可以按以下方式构建:
//Default car:
Car car = new CarBuilder().build();

//Customised car:
Car car = new CarBuilder().withFuelType(Car.Fuel.DEISEL).withGearBox(Car.Gears.AUTOMATIC).build();

感谢@StuPointerException提供的答案,非常有帮助,点赞! - Haifeng Zhang
拥有私有字段,然后再拥有访问器方法的意义何在?公共字段和访问器方法实现了相同的有效结果,这不需要图论高级学位来理解。此外,您不需要 CarBuilder 对象,只需调用 new Car(this.fuel, this.gears) 即可。 - Above The Gods
@AboveTheGods,它们具有相同的有效结果,但隐藏私有字段可以提供额外的好处(请参见https://dev59.com/dmgu5IYBdhLWcg3wy598以获取一些好的观点)。关于Car的构造函数,您是正确的,我应该将其设置为私有,这样汽车只能在构建器内部构建,这封装了构建逻辑并确保汽车只能按照有效规格构建。 - StuPointerException

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接