我车库里真的有车吗?

时间:2014-07-22 09:01:09

标签: java inheritance

我是Java编程的新手,试图掌握OOP。

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

public abstract class Vehicle{....}

和2个子类:

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

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

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

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

我对多态性感到非常满意,直到我尝试访问其中一个Car独有的字段,例如:

boolean carIsAutomatic = myGarage[0].auto;

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

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

这有效......但它对方法没有帮助,只是字段。意思是我不能做

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

所以我的问题是 - 我的车库里到底有什么?我试图获得直觉以及了解“幕后”发生了什么。


为了未来的读者,请简要总结以下答案:

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

13 个答案:

答案 0 :(得分:147)

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

例如:

public class Garage {
    private List<Car> cars;
    private List<Boat> boats;
}

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

为什么会有多态?

让我们说Vehicle就像:

public abstract class Vehicle {
   protected int price;
   public getPrice() { return price; }
   public abstract int getPriceAfterYears(int years);
}

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

然而,确定n年后价格的公式取决于车辆,因此由实施小组来定义它。例如:

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课程,您可以定义:

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

多态性的兴趣是能够在getPriceAfterYears 上调用Vehicle,而无需关注实施。

通常,向下转向是设计有缺陷的标志:如果您需要区分其实际类型,请不要将您的车辆全部存放在一起。

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

答案 1 :(得分:84)

要回答您的问题,您可以通过以下方式了解车库的确切内容:

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

更新:正如您可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但这不是一个好的做法,特别是如果你的车库里有大量的车辆。所以只有当你知道车库会保持小的时候才使用它。如果情况并非如此,请搜索&#34;避免实例&#34;在堆栈溢出时,有多种方法可以做到。

答案 2 :(得分:22)

如果您使用基本类型,则只能访问它的公共方法和字段。

如果你想访问扩展类型,但是有一个存储它的基本类型的字段(如你的情况),你首先必须强制转换它然后你可以访问它:

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

在没有临时场的情况下更短:

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

由于您使用的是Vehicle个对象,因此您只能在不使用强制转换的情况下从基类调用方法。因此,对于你的车库,建议区分不同阵列中的对象 - 或者更好的列表 - 数组通常不是一个好主意,因为它在处理方面的灵活性远远低于基于Collection的类

答案 3 :(得分:13)

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

回答评论:

接口是指描述如何与外部世界通信的合同。在合同中,您定义您的车辆可以移动,可以操纵,但是您没有描述它将如何实际工作,它在实现中描述。通过抽象类,您可能具有共享某些实现的功能,但您也有你不知道如何实施的功能。

使用抽象类的一个例子:

    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,船可能会使用声纳识别它的位置。

答案 4 :(得分:13)

  

我是Java编程的新手,试图掌握OOP。

仅仅是我的2美分 - 我会尝试缩短它,因为已经说过许多有趣的事情。但事实上,这里有两个问题。一个关于&#34; OOP&#34;以及如何用Java实现它。

首先,是的,你在你的车库里有一辆车。所以你的假设是正确的。但是,Java是一种静态类型语言。并且编译器中的类型系统只能知道&#34;各种对象的类型由相应的声明组成。不是他们的用法。如果您有一个Vehicle数组,编译器只知道它。因此,它会检查您是否仅在任何 Vehicle上执行操作。 (换句话说,Vehicle声明中可见方法属性

你可以向编译器解释&#34;你实际上知道这个VehicleCar&#34; ,通过使用显式转换{{ 1}}。编译器会相信你 - 即使 in Java 在运行时进行检查,这可能会导致(Car)阻止进一步损坏,如果你撒谎 (像C ++这样的其他语言在运行时不会检查 - 你必须知道你做了什么)

最后,如果你真的需要,你可以依靠运行时类型识别(即:ClassCastException)来检查&#34;真实的&#34;尝试强制转换之前的对象类型。但这在Java中被认为是一种不好的做法。

正如我所说,这是实现OOP的Java方式。有完全不同的 class 系列语言,通常称为&#34;动态语言&#34; 仅在运行时检查-time 如果对象是否允许操作。使用这些语言,您不需要向上移动&#34;一些(可能是抽象的)基类的所有常用方法,以满足类型系统。这称为duck typing

答案 5 :(得分:9)

你问过你的管家:

   Jeeves,还记得我在爪哇岛的车库吗?检查停在那里的第一辆车是否是自动的。

懒惰的Jeeves说:

  

但先生,如果它是一辆不能自动或非自动的车辆怎么办?

这就是全部。

好吧,这并不是全部,因为现实比静态打字更像鸭子。这就是我说Jeeves懒惰的原因。

答案 6 :(得分:8)

您的问题处于更基础的层面:您构建Vehicle的方式Garage需要了解更多关于其对象的信息,而不是Vehicle接口。您应该尝试从Vehicle角度构建Garage类(并且通常从将要使用的所有内容Vehicle的角度来看):它们是什么类型的东西需要与他们的车辆?如何用我的方法使这些事情成为可能?

例如,从您的示例:

bool carIsAutomatic = myGarage[0].auto;

你的车库想要了解车辆发动机的原因吗?无论如何,Car没有必要公开这个。您仍然可以在isAutomatic()中公开未实现的Vehicle方法,然后将其return True中的Boatreturn this.auto中的Car实现。

拥有一个三值EngineType枚举(HAS_NO_GEARSHAS_GEARS_AUTO_SHIFTHAS_GEARS_MANUAL_SHIFT)会更好,这会让你的代码推理实际特征一般的Vehicle干净准确。 (无论如何,你需要这种区别来处理摩托车。)

答案 7 :(得分:7)

你的车库包含车辆,所以你有车辆的编译器静态控制视图和.auto是一个你无法访问它的Car字段,动态地它是一辆Car所以演员不会创建一些问题,如果它将是一个船,你试图使铸造到汽车将在运行时上升异常。

答案 8 :(得分:7)

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

这种模式的优点是你可以在超类的不同子类上调用不相关的代码,而不必在任何地方进行奇怪的转换,或者将大量不相关的方法放入超类中。

这可以通过创建Visitor对象并允许我们的Vehicle班级到accept()访问者来实现。

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

例如演示:

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允许您在BoatCar上调用不同的代码,具体取决于调用visit的实现。您还可以创建访问者的其他实现,以使用相同的.visit()模式调用不同的代码。

另请注意,使用此方法时,不会使用instanceof或任何hacky类检查。类之间唯一重复的代码是方法void accept(Visitor)

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

答案 9 :(得分:6)

我真的只是在这里汇集其他人的想法(我不是Java人,所以这是假的而不是实际的)但是,在这个人为的例子中,我会将我的汽车检查方法抽象为专用上课,只知道汽车,只关心汽车看车库:

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。感谢三国的Enum,您现在可以知道它是自动的还是不是汽车。

当然,您需要为您关注的每辆车提供不同的VehicleInspectors。而你刚刚解决了哪个VehicleInspector实例化链的问题。

相反,您可能希望查看接口。

摘要getTransmission到界面(例如HasTransmission)。这样,您可以检查车辆是否有传输,或者写一个TransmissionInspector:

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

现在你说,你只关于传输,无论车辆,所以可以问TransmissionInspector。可以通过TransmissionInspector检查总线和汽车,但它只能询问传输。

现在,您可能会认为布尔值并不是您所关心的。此时,您可能更喜欢使用通用的Supported类型,它公开支持的状态和值:

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

现在您的Inspector可能被定义为:

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人,所以上面的一些语法可能是错误的,但概念应该成立。尽管如此,如果没有先测试,请不要将上述内容放在任何重要位置。

答案 10 :(得分:2)

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

public abstract class Vehicle {
    public final boolean isCar;
    public final boolean isBoat;

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

将继承类中的“车辆级别”字段设置为

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

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

实施使用车辆级别字段正确解读车辆类型。

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

由于您告诉编译器车库中的所有内容都是车辆,因此您坚持使用Vehicle类级方法和字段。如果要正确解密Vehicle类型,则应设置一些类级别字段,例如isCarisBoat会让您让程序员更好地了解您正在使用的车辆类型。

Java是一种类型安全的语言,因此在处理像BoatCar s一样的数据之前,最好始终键入check。

答案 11 :(得分:2)

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

答案 12 :(得分:0)

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

我知道正确建模一个类来代表一个车库将无助于回答你的“车库中的汽车”问题;只是一条建议。

回到代码。除了对OOP有所了解之外,一些问题对于创建一个场景会有所帮助,从而更好地理解你想要解决的问题(假设有一个问题,而不仅仅是“挂起”):

  1. 谁或什么想要了解carIsAutomatic
  2. 给定carIsAutomatic,执行doSomeCarStuff的人或者是什么?
  3. 可能是一些检查员,或者只知道如何驾驶自动变速车等的人,但从车库的角度来看,它只知道它拥有一些车辆,因此(在这个模型中)它是责任这名检查员或司机告诉他们这是一辆汽车还是一艘船;此时,您可能想要开始创建另一组类来表示场景中类似类型的* actor * s。取决于要解决的问题,如果你真的需要,你可以把车库建模成一个超级智能系统,所以它的行为就像一个自动售货机,而不是一个普通的车库,有一个按钮说“汽车”,另一个说“船”,以便人们可以按下按钮来获得他们想要的汽车或船只,这反过来使这个超级智能车库负责告诉应该向用户呈现什么(汽车或船);为了遵循这种即兴创作,车库在接受车辆时可能需要一些簿记,有人可能需要提供信息等,所有这些责任都超出了简单的 Main 类。

    说了这么多,当然我理解所有麻烦,以及样板,编写一个OO程序,特别是当它试图解决的问题非常简单时,OO确实是解决许多其他问题的可行方法。根据我的经验,通过一些输入提供用例,人们开始设计场景如何相互交互,将它们分类为类(以及Java中的接口),然后使用类似 Main 的内容用于引导世界的类