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

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

public abstract class Vehicle{....}

和2个子类:

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

Car和Boat还拥有一些不常见的独特字段和方法(没有相同的名称,所以我无法在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;

编译器不接受。我使用类型转换解决了这个问题:

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

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

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

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


为了方便以后的读者,以下是对答案的简要总结:

Yes, there's a Car in myGarage[] Being a static typed language, the Java compiler will not lend access to methods/fields that are non-"Vehicle", if accessing those through a data structure based on the Vehicle super class( such as Vehicle myGarage[]) As for how to solve, there are 2 main approaches below: Use type casting, which will ease the compiler's concerns and leave any errors in the design to run time The fact that I need casting says the design is flawed. If I need access to non-Vehicle capabilities then I shouldn't be storing the Cars and Boats in a Vehicle based data structure. Either make all those capabilities belong to Vehicle, or use more specific (derived) type based structures In many cases, composition and/or interfaces would be a better alternative to inheritance. Probably the subject of my next question... Plus many other good insights down there, if one does have the time to browse through the answers.


当前回答

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

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

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

将继承类中的Vehicle level字段设置为适当的值。

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类级别的方法和字段。如果你想正确地破译车辆类型,那么你应该设置一些类级别的字段,例如isCar和isBoat,这将使你的程序员更好地理解你正在使用的车辆类型。

Java是一种类型安全的语言,所以在处理像Boats和Cars这样已转换的数据之前,最好总是进行类型检查。

其他回答

你在这里的问题是在一个更基本的层面上:你以这样一种方式构建了Vehicle,车库需要了解更多关于它的对象的信息,而不是Vehicle接口所提供的信息。您应该尝试从Garage透视图来构建Vehicle类(通常从所有将使用Vehicle的东西的透视图来构建):他们需要用他们的车辆做什么样的事情?我怎样才能用我的方法实现这些呢?

例如,从你的例子:

bool carIsAutomatic = myGarage[0].auto;

你的车库想知道一辆车的发动机…原因吗?不管怎样,没有必要只是通过Car来曝光。你仍然可以在Vehicle中暴露一个未实现的isAutomatic()方法,然后在Boat中实现它为return True并返回this。Car中的auto。

如果有一个三值的EngineType enum (HAS_NO_GEARS, HAS_GEARS_AUTO_SHIFT, HAS_GEARS_MANUAL_SHIFT)会更好,这会让你的代码清晰而准确地推断出通用车辆的实际特征。(无论如何,你需要区分摩托车。)

要回答你的问题,你可以找到你的车库里到底有什么,你可以做下面的事情:

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

更新:正如你可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但它不是一个好的实践,特别是如果你的车库里有大量的车辆。所以,只有在你知道车库不会太大的情况下才使用它。如果不是这样,在堆栈溢出时搜索“avoid instanceof”,有多种方法可以做到这一点。

这是应用访问者设计模式的好地方。

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

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

您还可以创建许多类型的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允许您在Boat或Car上调用不同的代码,这取决于调用哪个访问实现。您还可以创建Visitor的其他实现,以使用相同的.visit()模式调用不同的代码。

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

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

我真的只是汇集了其他人的想法(我不是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就行了。多亏了三状态枚举,你现在可以知道它是自动的,甚至它不是一辆车。

当然,你需要为你关心的每辆车使用不同的vehicleinspector。你已经把哪个VehicleInspector实例化的问题推到了链上。

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

抽象getTransmission到接口(例如hastrtransmission)。这样,你就可以检查车辆是否有变速器,或者写一个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可以检查公共汽车和汽车,但它只能询问有关变速器的信息。

现在,您可能认为布尔值并不是您所关心的全部。在这一点上,你可能更喜欢使用泛型的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;
    }
}

现在你的检查器可能被定义为:

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人,所以上面的一些语法可能是错误的,但这些概念应该是成立的。然而,在没有测试之前,不要在任何重要的地方运行上面的代码。

You defined that your garage will store vehicles, so you do not care what type of vehicles you have. The vehicles have common features like engine, wheel, behavior like moving. The actual representation of these features might be different, but at abstract layer are the same. You used abstract class which means that some attributes, behaviors are exactly the same by both vehicle. If you want to express that your vehicles have common abstract features then use interface like moving might mean different by car and boat. Both can get from point A to point B, but in a different way (on wheel or on water - so the implementation will be different) So you have vehicles in the garage which behave the same way and you do not car about the specific features of them.

回答评论:

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

一个使用抽象类的例子:

    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,船只可能会使用声纳来确定它的位置。