首页 文章

我车库里真的有车吗? [重复]

提问于
浏览
259

这个问题在这里已有答案:

我是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();

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


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

  • 是的, myGarage[] 中有一个 Car

  • 作为静态类型语言,如果通过基于Vehicle超类的数据结构(例如 Vehicle myGarage[] )访问那些方法/字段,Java编译器将不会访问非"Vehicle"的方法/字段 .

  • 至于如何解决,有以下两种主要方法:

  • 使用类型转换,这将减轻编译器的顾虑,并将设计中的任何错误留给运行时

  • 我需要铸造的事实说设计是有缺陷的 . 如果我需要访问非车辆功能,那么我不应该将汽车和船只存储在基于车辆的数据结构中 . 要么使所有这些功能属于Vehicle,要么使用更具体(派生)类型的结构

  • 在许多情况下,组合和/或接口将是继承的更好替代方案 . 可能是我下一个问题的主题......

  • 如果有人有时间浏览答案,还有很多其他好的见解 .

13 回答

  • 7

    如果您需要在车库中区分 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 .

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

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

  • 0

    要回答您的问题,您可以了解您的车库究竟是什么,您可以执行以下操作:

    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”,有多种方法可以执行此操作 .

  • 2

    如果您对基本类型进行操作,则只能访问它的公共方法和字段 .

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

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

    或者没有临时场的时间更短

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

    由于您使用的是 Vehicle 对象,因此只能在其上调用基类中的方法而不进行强制转换 . 因此,对于你的车库,建议区分不同阵列中的对象 - 或更好的列表 - 数组通常不是一个好主意,因为它在处理方面远不如基于 Collection 的类 .

  • 8

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

    回答评论:

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

    使用抽象类的一个例子:

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

  • 9

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

    仅仅是我的2美分 - 我会尝试缩短它,因为已经说过许多有趣的事情 . 但事实上,这里有两个问题 . 一个关于“OOP”,一个关于如何在Java中实现它 .

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

    你可以通过使用显式转换 (Car) 向编译器解释“你实际上知道这个 VehicleCar ” . 编译器会相信你 - 即使在Java中运行时有一个检查,这可能导致 ClassCastException 阻止进一步损害如果你撒谎(其他语言如C不会在运行时检查 - 你必须知道你做了什么)

    最后,如果您确实需要,可以依赖运行时类型标识(即: instanceof )来检查对象的"real"类型,然后再尝试强制转换它 . 但这在Java中被认为是一种不好的做法 .

    正如我所说,这是实现OOP的Java方式 . 有一个完整的不同类语言系列,通常称为"dynamic languages",它只在运行时检查是否允许对象进行操作 . 使用这些语言,您不需要"move up"一些(可能是抽象的)基类的所有常用方法来满足类型系统 . 这叫做duck typing .

  • 2

    你问过你的管家:

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

    和懒惰的Jeeves说:

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

    就这样 .

    好吧,这并不是全部,因为现实比静态类型更像鸭子类型 . 这就是我说Jeeves很懒的原因 .

  • 13

    这里你的问题处于一个更基础的层面:你 Build 了 Vehicle ,以便 Garage 需要了解更多关于它的对象,而不是 Vehicle 接口 . 您应该尝试从 Garage 角度构建 Vehicle 类(通常从所有将要使用的东西的角度来看 Vehicle ):他们需要对他们的车辆做什么样的事情?如何用我的方法使这些事情成为可能?

    例如,从您的示例:

    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 的实际特征 . (无论如何,你需要这种区别来处理摩托车 . )

  • 7

    你的车库包含车辆,所以编译器静态控制视图你有一个车辆和.auto是一个你无法访问它的Car字段,动态它是一个Car所以演员不会产生一些问题,如果它将是一艘船,你试图让施放到Car将在运行时引发异常 .

  • 84

    这是应用 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 的实现 . 您还可以创建Visitor的其他实现来调用不同的代码相同的 .visit() 模式 .

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

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

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

  • 13

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

  • 147

    Create 车辆级别字段,有助于使每个车辆更加独特 .

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

    Set 继承类中的Vehicle级别字段为适当的值 .

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

    Implement 使用车辆级别字段正确解读车辆类型 .

    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 一样的数据之前,最好始终键入check .

  • 22

    建模你想要在程序中呈现的对象(为了解决一些问题)是一回事,编码是另一回事 . 在你的代码中,我认为它本质上通常被认为是对象,尽管它们看起来确实如此,通常是出于自包含的语言完整性并提供一些熟悉性,但数组作为一种类型是真的只是一个特定于计算机的东西,恕我直言,特别是在Java中,你无法扩展数组 .

    我明白正确建模一个类来代表一个车库将无法回答你的“车库中的汽车”问题;只是一条建议 .

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

    • 谁或想要了解 carIsAutomatic

    • 给定 carIsAutomatic ,谁或什么表演 doSomeCarStuff

    可能是一些检查员,或者只知道如何驾驶汽车等车辆的人,但是从车库或船上开车;此时,您可能想要开始创建另一组类来表示场景中类似类型的* actor * s . 取决于要解决的问题,如果你真的需要,你可以把车库建模为一个超级智能系统,所以它的行为就像一个自动售货机,而不是一个普通的车库,有一个按钮说"Car"和另一个说"Boat",因此,人们可以按下按钮来获得他们想要的汽车或船只,这反过来使这个超级智能车库负责告诉应该向用户提供什么(汽车或船);为了跟随这种即兴创作,车库在接受车辆时可能需要一些簿记,有人可能需要提供信息等,所有这些责任都超出了简单的主类 .

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

相关问题