关于java:我车库里真的有车吗?

Do I really have a car in my garage?

本问题已经有最佳答案,请猛点这里访问。

我是一个Java编程新手,试图获得OOP的诀窍。

所以我创建了这个抽象类:

1
public abstract class Vehicle{....}

和2个子类:

1
2
public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}

CarBoat还包含一些不常见的独特字段和方法(没有相同的名称,因此我无法在车内为它们定义抽象方法)。

现在我在Mainclass设置了我的新车库:

1
2
3
Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);

我对多态性非常满意,直到我尝试访问汽车特有的领域之一,例如:

1
boolean carIsAutomatic = myGarage[0].auto;

编译器不接受。我使用Casting解决了这个问题:

1
boolean carIsAutomatic = ((Car)myGarage[0]).auto;

那是有效的…但它对方法没有帮助,只是字段。意思是我做不到

1
(Car)myGarage[0].doSomeCarStuff();

所以我的问题是——我的车库里到底有什么?我试着获得直觉,并理解"幕后"发生了什么。

为了将来的读者,以下是答案的简短总结:

  • 是的,在myGarage[]中有一个Car
  • 作为一种静态类型的语言,Java编译器将不提供对非"车辆"的方法/字段的访问,如果通过基于车辆超级类的数据结构访问这些字段/字段(例如EDCOX1(4))
  • 关于如何解决,主要有两种方法:
  • 使用类型转换,这将减轻编译器的顾虑,并将设计中的任何错误留给运行时。
  • 我需要铸造的事实表明设计有缺陷。如果我需要访问非车辆功能,那么我不应该将汽车和船只存储在基于车辆的数据结构中。要么使所有这些能力属于车辆,要么使用更具体的(派生的)基于类型的结构
  • 在许多情况下,组合和/或接口将是继承的更好选择。可能是我下一个问题的主题…
  • 如果你有时间浏览答案的话,还有很多其他很好的见解。

  • 如果您需要在车库中区分CarBoat,那么您应该将它们存储在不同的结构中。

    例如:

    1
    2
    3
    4
    public class Garage {
        private List<Car> cars;
        private List<Boat> boats;
    }

    然后,您可以定义特定于船只或特定于汽车的方法。

    为什么有多态性呢?

    假设Vehicle是:

    1
    2
    3
    4
    5
    public abstract class Vehicle {
       protected int price;
       public getPrice() { return price; }
       public abstract int getPriceAfterYears(int years);
    }

    每个Vehicle都有一个价格,因此它可以放在Vehicle抽象类中。

    然而,决定n年后价格的公式取决于车辆,因此它留给实现类来定义它。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public Car extends Vehicle {
        // car specific
        private boolean automatic;
        @Override
        public getPriceAfterYears(int years) {
            // losing 1000$ every year
            return Math.max(0, this.price - (years * 1000));  
        }
    }

    Boat类可能对getPriceAfterYears和特定属性和方法有其他定义。

    所以现在回到Garage类中,您可以定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // car specific
    public int numberOfAutomaticCars() {
        int s = 0;
        for(Car car : cars) {
            if(car.isAutomatic()) {
                s++;
            }
        }
        return s;
    }
    public List<Vehicle> getVehicles() {
        List<Vehicle> v = new ArrayList<>(); // init with sum
        v.addAll(cars);
        v.addAll(boats);
        return v;
    }
    // all vehicles method
    public getAveragePriceAfterYears(int years) {
        List<Vehicle> vehicules = getVehicles();
        int s = 0;
        for(Vehicle v : vehicules) {
            // call the implementation of the actual type!
            s += v.getPriceAfterYears(years);  
        }
        return s / vehicules.size();
    }

    多态性的好处是能够在不关心实现的情况下调用getPriceAfterYearsVehicle上。

    通常,下压铸件是一个有缺陷的设计的标志:如果你需要区分车辆的实际类型,不要把你的车辆放在一起。

    注:当然,这里的设计很容易改进。这只是一个例子来说明这些要点。


    要回答您的问题,您可以了解车库中的具体内容,请执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Vehicle v = myGarage[0];

    if (v instanceof Car) {
       // This vehicle is a car
       ((Car)v).doSomeCarStuff();
    } else if(v instanceof Boat){
       // This vehicle is a boat
       ((Boat)v).doSomeBoatStuff();
    }

    更新:正如您可以从下面的评论中看到的,这种方法对于简单的解决方案来说是可以的,但是它不是一个好的实践,特别是如果您的车库中有大量的车辆。所以只有当你知道车库很小的时候才能使用它。如果不是这样,那么在堆栈溢出时搜索"避免instanceof",有多种方法可以做到这一点。


    如果在基类型上操作,则只能访问其公共方法和字段。

    如果要访问扩展类型,但有一个存储它的基类型字段(如您的示例所示),则必须先强制转换它,然后才能访问它:

    1
    2
    Car car = (Car)myGarage[0];
    car.doSomeCarStuff();

    或更短,无温度场:

    1
    ((Car)myGarage[0]).doSomeCarStuff();

    由于您使用的是Vehicle对象,因此只能从基类调用方法,而无需强制转换。因此,对于您的车库来说,区分不同数组或更好的列表中的对象可能是明智的,因为数组在处理方面远不如基于Collection的类灵活。


    I'm a newbie to Java programming, trying to get the hang of OOP.

    只要我的2美分-我会尽量缩短,因为很多有趣的事情已经说了。但事实上,这里有两个问题。一个关于"OOP"和一个关于它是如何在Java中实现的。

    首先,是的,你的车库里有辆车。所以你的假设是正确的。但是,Java是一种静态类型的语言。编译器中的类型系统只能通过相应的声明"知道"各种对象的类型。不是他们的习惯。如果您有一个Vehicle数组,编译器只知道这一点。因此,它将检查您是否只在任何Vehicle上执行允许的操作。(换句话说,在Vehicle声明中可见的方法和属性)。

    通过使用显式强制转换(Car),可以向编译器解释"您实际上知道这个VehicleCar"。编译器会相信你——即使在Java中运行时有一个检查,也可能会导致EDOCX1"24",防止如果你说谎"子>"(其他语言如C++在运行时不检查-你必须知道你所做的事情)< /Sub >。

    最后,如果您确实需要的话,您可能需要依赖运行时类型标识(即:instanceof)来检查对象的"真实"类型,然后再尝试对其进行强制转换。但这在Java中被认为是一个糟糕的做法。

    正如我所说的,这是Java实现OOP的方式。有完全不同的语言系列,通常称为"动态语言",仅在运行时检查对象上是否允许操作。使用这些语言,您不需要将所有常用方法"上移"到某个(可能是抽象的)基类以满足类型系统。这叫鸭子打字。


    您定义了您的车库将存储车辆,因此您不关心您拥有的车辆类型。汽车有发动机、车轮、运动等共同特征。这些特性的实际表示可能不同,但在抽象层上是相同的。您使用了抽象类,这意味着两辆车的某些属性和行为完全相同。如果你想表达你的车辆有共同的抽象特征,那么像移动这样的界面可能意味着汽车和船的不同。两者都可以从A点到B点,但以不同的方式(在车轮上或水上-因此实施将有所不同)所以车库里的车辆也有相同的行为方式,你不会因为它们的特定特征而开车。

    回答意见:

    接口是指描述如何与外部世界通信的合同。在合同中,您定义了您的车辆可以移动、可以转向,但您没有描述它实际的工作方式,而是在实现中进行了描述。通过抽象类,您可能拥有共享一些实现的函数,但您也拥有不知道如何实现的函数。

    使用抽象类的一个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        abstract class Vehicle {

        protected abstract void identifyWhereIAm();
        protected abstract void startEngine();
        protected abstract void driveUntilIArriveHome();
        protected abstract void stopEngine();

        public void navigateToHome() {
            identifyWhereIAm();
            startEngine();
            driveUntilIArriveHome();
            stopEngine();
        }
    }

    您将对每辆车使用相同的步骤,但这些步骤的实施将因车辆类型而异。汽车可能使用GPS,船可能使用声纳来识别它在哪里。


    你问你的管家:

    Jeeves, remember my garage on the Isle of Java? Go check whether the first vehicle parked there is automatic.

    懒惰的吉维斯说:

    but sir, what if it's a vehicle that can't be automatic or non-automatic?

    这就是全部。

    ok,这并不是全部,因为现实比静态类型更倾向于duck类型。这就是我为什么说吉维斯懒惰的原因。


    这里的问题在一个更基本的层面上:您构建Vehicle的方式使得Garage需要比Vehicle接口提供更多关于其对象的信息。你应该试着从Garage的角度(通常从使用Vehicle的所有方面)构建Vehicle类:他们需要用他们的车辆做什么?我怎样才能用我的方法使这些事情成为可能?

    例如,从您的示例中:

    1
    bool carIsAutomatic = myGarage[0].auto;

    你的车库想知道一辆车的引擎…原因?不管怎么说,没有必要只让Car曝光。您仍然可以在Vehicle中暴露未实现的isAutomatic()方法,然后在Boat中实现为return True,在Car中实现为return this.auto

    如果有一个三值的EngineType枚举(HAS_NO_GEARSHAS_GEARS_AUTO_SHIFTHAS_GEARS_MANUAL_SHIFT,这会更好,这样您的代码就可以清楚、准确地解释通用Vehicle的实际特性。(不管怎样,你需要这种区别来处理摩托车。)


    这是应用Visitor设计模式的好地方。

    这种模式的好处是,您可以在一个超类的不同子类上调用不相关的代码,而不必到处执行奇怪的强制转换,也不必将大量不相关的方法放入超类中。

    这是通过创建一个Visitor对象并允许我们的Vehicle类访问accept()来实现的。

    您还可以创建许多类型的Visitor,并使用相同的方法调用无关的代码,只是使用不同的Visitor实现,这使得这种设计模式在创建干净的类时非常强大。

    例如,演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    public class VisitorDemo {

        // We'll use this to mark a class visitable.
        public static interface Visitable {

            void accept(Visitor visitor);
        }

        // This is the visitor
        public static interface Visitor {

            void visit(Boat boat);

            void visit(Car car);

        }

        // Abstract
        public static abstract class Vehicle implements Visitable {

                // NO OTHER RANDOM ABSTRACT METHODS!

        }

        // Concrete
        public static class Car extends Vehicle {

            public void doCarStuff() {
                System.out.println("Doing car stuff");
            }

            @Override
            public void accept(Visitor visitor) {
                visitor.visit(this);
            }

        }

        // Concrete
        public static class Boat extends Vehicle {

            public void doBoatStuff() {
                System.out.println("Doing boat stuff");
            }

            @Override
            public void accept(Visitor visitor) {
                visitor.visit(this);
            }

        }

        // Concrete visitor
        public static class StuffVisitor implements Visitor {

            @Override
            public void visit(Boat boat) {
                boat.doBoatStuff();
            }

            @Override
            public void visit(Car car) {
                car.doCarStuff();
            }
        }

        public static void main(String[] args) {
            // Create our garage
            Vehicle[] garage = {
                new Boat(),
                new Car(),
                new Car(),
                new Boat(),
                new Car()
            };

            // Create our visitor
            Visitor visitor = new StuffVisitor();

            // Visit each item in our garage in turn
            for (Vehicle v : garage) {
                v.accept(visitor);
            }
        }

    }

    如您所见,StuffVisitor允许您根据调用visit的实现,在BoatCar上调用不同的代码。您还可以创建访问者的其他实现,以使用相同的.visit()模式调用不同的代码。

    还请注意,使用此方法时,没有使用instanceof或任何黑客类检查。类之间唯一重复的代码是方法void accept(Visitor)

    例如,如果您想支持3种类型的具体子类,您也可以将该实现添加到Visitor接口中。


    你的车库里有车辆,所以编译器静态控制视图显示你有一辆车和一辆车。汽车是一个汽车领域,你不能访问它,动态它是一辆汽车,所以演员不会造成一些问题,如果它将是一艘船,你试图使演员对汽车运行时会上升一个例外。


    我真的只是在这里汇集其他人的想法(我不是爪哇人,所以这是假的,而不是实际的),但是,在这个设计的例子中,我将我的汽车检查方法抽象成一个专用的类,它只知道汽车,只关心车库时的汽车:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    abstract class Vehicle {
        public abstract string getDescription() ;
    }

    class Transmission {
        public Transmission(bool isAutomatic) {
            this.isAutomatic = isAutomatic;
        }
        private bool isAutomatic;
        public bool getIsAutomatic() { return isAutomatic; }
    }

    class Car extends Vehicle {
        @Override
        public string getDescription() {
            return"a car";
        }

        private Transmission transmission;

        public Transmission getTransmission() {
            return transmission;
        }
    }

    class Boat extends Vehicle {
        @Override
        public string getDescription() {
            return"a boat";
        }
    }

    public enum InspectionBoolean {
        FALSE, TRUE, UNSUPPORTED
    }

    public class CarInspector {
        public bool isCar(Vehicle v) {
            return (v instanceof Car);
        }
        public bool isAutomatic(Car car) {
            Transmission t = car.getTransmission();
            return t.getIsAutomatic();
        }
        public bool isAutomatic(Vehicle vehicle) {
            if (!isCar(vehicle)) throw new UnsupportedVehicleException();
            return isAutomatic((Car)vehicle);
        }
        public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
            if (!isCar(garage[bay])) return InspectionBoolean.UNSUPPORTED;
            return isAutomatic(garage[bay])
                 ? InspectionBoolean.TRUE
                 : InspectionBoolean.FALSE;
        }
    }

    关键是,你已经决定,当你询问汽车的传动系统时,你只关心汽车。所以你只要问一下Carinspector就行了。多亏了三态枚举,您现在可以知道它是自动的还是非汽车的。

    当然,对于您关心的每辆车,您需要不同的车辆检查员。你刚刚把问题推到了哪辆车的检测者来例示这个链条上。

    因此,您可能需要查看接口。

    getTransmission抽象为接口(例如HasTransmission)。这样,您可以检查车辆是否有变速器,或编写变速器检测仪:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    abstract class Vehicle { }

    class Transmission {
        public Transmission(bool isAutomatic) {
            this.isAutomatic = isAutomatic;
        }
        private bool isAutomatic;
        public bool getIsAutomatic() { return isAutomatic; }
    }

    interface HasTransmission {
        Transmission getTransmission();
    }

    class Car extends Vehicle, HasTransmission {
        private Transmission transmission;

        @Override
        public Transmission getTransmission() {
            return transmission;
        }
    }

    class Bus extends Vehicle, HasTransmission {
        private Transmission transmission;

        @Override
        public Transmission getTransmission() {
            return transmission;
        }
    }

    class Boat extends Vehicle { }

    enum InspectionBoolean {
        FALSE, TRUE, UNSUPPORTED
    }

    class TransmissionInspector {
        public bool hasTransmission(Vehicle v) {
            return (v instanceof HasTransmission);
        }
        public bool isAutomatic(HasTransmission h) {
            Transmission t = h.getTransmission();
            return t.getIsAutomatic();
        }
        public bool isAutomatic(Vehicle v) {
            if (!hasTranmission(v)) throw new UnsupportedVehicleException();
            return isAutomatic((HasTransmission)v);
        }
        public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
            if (!hasTranmission(garage[bay])) return InspectionBoolean.UNSUPPORTED;
            return isAutomatic(garage[bay])
                 ? InspectionBoolean.TRUE
                 : InspectionBoolean.FALSE;
        }
    }

    现在你说的是,你只关心变速器,不管车辆如何,所以可以问变速器检测仪。汽车和公共汽车都可以由变速器检测仪检测,但只能询问有关变速器的情况。

    现在,您可能决定布尔值不是您所关心的全部。此时,您可能更喜欢使用通用的受支持类型,它公开受支持的状态和值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Supported<T> {
        private bool supported = false;
        private T value;

        public Supported() { }
        public Supported(T value) {
            this.isSupported = true;
            this.value = value;
        }

        public bool isSupported() { return supported; }
        public T getValue() {
            if (!supported) throw new NotSupportedException();
            return value;
        }
    }

    现在,您的检查员可以定义为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class TransmissionInspector {
        public Supported<bool> isAutomatic(Vehicle[] garage, int bay) {
            if (!hasTranmission(garage[bay])) return new Supported<bool>();
            return new Supported<bool>(isAutomatic(garage[bay]));
        }

        public Supported<int> getGearCount(Vehicle[] garage, int bay) {
            if (!hasTranmission(garage[bay])) return new Supported<int>();
            return new Supported<int>(getGearCount(garage[bay]));
        }
    }

    正如我所说的,我不是Java的家伙,所以上面的一些语法可能是错误的,但是概念应该成立。不过,在没有首先测试的情况下,不要在任何重要的地方运行上面的代码。


    如果您使用Java,可以使用反射来检查函数是否可用并执行它。


    创建车辆级别字段,有助于使每个单独的车辆更加独特。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class Vehicle {
        public final boolean isCar;
        public final boolean isBoat;

        public Vehicle (boolean isCar, boolean isBoat) {
            this.isCar  = isCar;
            this.isBoat = isBoat;
        }
    }

    将继承类中的车辆级别字段设置为适当的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Car extends Vehicle {
        public Car (...) {
            super(true, false);
            ...
        }
    }

    public class Boat extends Vehicle {
        public Boat (...) {
            super(false, true);
            ...
        }
    }

    使用"Vehicle Level(车辆级别)"字段正确地解密车辆类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    boolean carIsAutomatic = false;

    if (myGarage[0].isCar) {
        Car car = (Car) myGarage[0];
        car.carMethod();
        carIsAutomatic = car.auto;
    }

    else if (myGarage[0].isBoat) {
        Boat boat = (Boat) myGarage[0];
        boat.boatMethod();
    }

    由于您告诉编译器车库中的所有东西都是一辆车,所以您一直坚持使用车辆类级别的方法和字段。如果您想正确地破译车辆类型,那么您应该设置一些类级字段,例如isCarisBoat,这将使程序员更好地了解您使用的车辆类型。

    Java是一种类型安全的语言,因此最好在处理像EDCOX1、4和S和EDCOX1×5 s这样的数据之前总是键入检查。


    要在程序中呈现的建模对象(为了解决某些问题)是一回事,编码是另一回事。在您的代码中,我认为使用数组对车库建模本质上是不合适的。数组不应该被视为对象,尽管它们确实是,通常是为了自给自足的语言的完整性,提供一些熟悉性,但是数组作为一种类型实际上只是一个计算机特定的东西,特别是在Java中,你不能扩展数组。

    我知道,正确地模拟一个班级来代表一个车库不会有助于回答你的"车库里的汽车"问题,只是一条建议。

    回到代码。除了获得一些挂起的OOP,一些问题将有助于创建一个场景,从而更好地理解您想要解决的问题(假设有一个问题,而不仅仅是"获得一些挂起"):

  • 谁或什么人想了解carIsAutomatic
  • 考虑到carIsAutomatic,谁或什么人会执行doSomeCarStuff
  • 可能是一些检查员,或者只知道如何驾驶自动变速器汽车的人,等等,但是从车库的角度来看,它只知道它拥有一些车辆,因此(在这个模型中),这个检查员或司机有责任告诉它是一辆车还是一艘船;此时此刻,你可能想开始创建另一组cl。表示场景中类似类型的*actor*s。取决于要解决的问题,如果你真的必须这样做,你可以把车库模型化为一个超级智能系统,这样它就像一台自动售货机,而不是一个普通的车库,有一个按钮说"车",另一个按钮说"船",这样人们就可以按这个按钮得到他们想要的车或船,这反过来又使得这个超级Intel负责告诉用户(汽车或船)应该呈现什么内容的疏忽车库;为了遵循这种即兴创作,车库在接受车辆时可能需要一些簿记,可能需要有人提供信息等,所有这些责任都超出了简单的主要类。

    说了这么多,我当然理解所有的问题,连同样板文件,编写一个OO程序,特别是当它试图解决的问题非常简单时,但是OO确实是解决许多其他问题的可行方法。根据我的经验,在一些输入提供用例的情况下,人们开始设计场景,对象之间如何相互作用,将它们分类为类(以及Java中的接口),然后使用像主类之类的东西来引导世界。