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

Posted

技术标签:

【中文标题】面向对象设计:可扩展和可维护的汽车商店系统【英文标题】:Object oriented Design: Scalable and maintainable car store system 【发布时间】:2020-11-14 18:13:40 【问题描述】:

我昨天面试,问了一个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());
    

面试官回答说我提供的解决方案不可扩展且难以维护,但没有提供详细信息,所以我仍然对自己犯了什么错误以及如何改进感到困惑。

谁能帮忙分享你的想法并指出我应该改进什么?

谢谢!

【问题讨论】:

我可能会向他提出比“好”更明确的要求。 您在要求方面过于复杂了,因为似乎不需要MovableCar:只需定义一个RaceCar 类;给它两个字段;给它一个构造函数。定义燃料和汽车字段值的枚举。但是可扩展性——我很难理解那里实际要求的是什么:每次需要实例时都调用构造函数;仅仅调用构造函数似乎是必要且足够的。 我不喜欢你使用普通的Strings 作为属性。更好地使用枚举(或其他东西,这取决于这些实例的实际使用方式)。而且我不太喜欢这些要求,因为它们并没有说明如何使用这些实例。这使得设计不仅仅是通用的东西变得困难。也许他们期望工厂模式或策略模式。谁知道.. @AndyTurner 感谢安迪的回复。我已经问过期望什么样的可扩展性并得到答案“添加坦克”。我没有提及细节,因为其中一位面试官看到了源代码,他说这不是他期望的答案。 这个问题对我来说模棱两可,我问了几个问题,但没有得到明确的答复。我认为它可能是开放的,因为“可扩展性”和“可维护性”对我来说太宽泛了。 【参考方案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());

如果这是预期的,那么只使用 lambdas 或命令模式也可以。

【讨论】:

感谢 Taha 的回答,+1! 这里的大多数类型都没有任何意义。传输策略没有逻辑!要求中没有规定。【参考方案2】:

我会用 2 个类来回答这个问题,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);
   

可扩展性和可维护性是通过以下事实来实现的,即如果需求发生变化,这些是未来需要更改的仅有的 2 个类。 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 提供答案,对您有很大帮助,+1! 拥有私有字段的意义何在,然后又拥有这些字段的访问器方法?不需要图论的高级学位就可以看到公共字段和访问器方法获得相同的有效结果。此外,您不需要 CarBuilder 对象。您可以直接拨打new Car(this.fuel, this.gears) @AboveTheGods 它们确实具有相同的有效结果,但隐藏私有字段可以提供额外的好处(请参阅***.com/questions/11071407/… 了解一些优点)。 Car 的 Constructor 是对的,我应该将其设为私有,因此唯一可以构建汽车的地方是在 builder 内,这封装了构建逻辑并确保汽车只能按照有效的规范构建。跨度>

以上是关于面向对象设计:可扩展和可维护的汽车商店系统的主要内容,如果未能解决你的问题,请参考以下文章

设计模式——面向对象设计原则

面向对象设计原则-概述

设计模式2 面向对象设计原则

面向对象设计原则概述

进阶面向对象

开闭原则——面向对象设计原则