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

Posted

技术标签:

【中文标题】我的车库里真的有车吗? [复制]【英文标题】:Do I really have a car in my garage? [duplicate] 【发布时间】:2014-09-13 00:28:10 【问题描述】:

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

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

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

这行得通……但它对方法没有帮助,只是对字段有帮助。意思是我做不到

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

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


为了未来的读者,以下答案的简短摘要:

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

【问题讨论】:

试试 ((Car)myGarage[0]).doSomeCarStuff(); 如果你告诉编译器你有一个 Vehicle[],为什么它会期望你实际上有一个 Car 或一个 Boat(或任何其他特定的派生类型)。它相信你——你有一个 Vehicles 数组(或者可能是从 Vehicle 派生的东西,但你已经明确声明你不想知道这些)。 你用的是哪个版本的java? boolean carIsAutomatic = (Car)myGarage[0].auto 无法为我编译 更重要的是......如果你不想像对待Vehicles 那样对待Cars,为什么要把它们塞进Vehicle[] @cHao 提出了一个很好的问题。如果您确实想检查vehicle 是否是自动的,而不是先检查它是否是汽车,那么您可以将auto 或更好的isAuto() 移动到Vehicle类,并让它始终为Boats 返回false。或者将isAuto() 推入一个仅由某些Vehicles 实现的接口,称为CanBeAutomatic 【参考方案1】:

我是 Java 编程的新手,正在尝试掌握 OOP。

就我的 2 美分——我会尽量缩短,因为很多有趣的事情已经说过了。但是,实际上这里有两个问题。一篇关于“OOP”,一篇关于它是如何在 Java 中实现的。

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

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

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

正如我所说,这是实现 OOP 的 Java 方式。有完全不同的 家族 被广泛称为“动态语言”只在运行时检查 是否允许对对象进行操作。使用这些语言,您无需将所有常用方法“向上移动”到某个(可能是抽象的)基类来满足类型系统。这称为duck typing。

【讨论】:

实际上鸭子类型在一些静态类型语言中也可用。示例包括 many languages from the ML family 和 Go language。 @user 显然,这过于简化了。有人可能会更进一步地说,像 Groovy 之类的一些动态语言是基于 JVM 的——证明这是否是必要的,而不是 Java 拥有成为动态语言所需的所有“东西”。关于 ML 和 Go,我不会讨论 type inferencestructural typing 是或不是 duck typing 的扩展。这里的重点只是说 "OOP""Java(实现 OOP 的方式)" 是两个不同的东西。如果 OP 的原始直觉是正确的,它就会与 Java OOP 模型发生冲突。【参考方案2】:

您的问题在更基本的层面上:您构建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 的实际特征,并且准确。 (无论如何,你需要这种区别来处理摩托车。)

【讨论】:

如果车库可能需要重新布置车辆并且各种车辆可能总是、从不或有时 [例如取决于值班服务员是否会开车] 需要拖车。只能由牵引车移动的车辆只能进入牵引车可以到达的空间;变速杆车辆不应放置在它们会“阻挡”自动变速器车辆的地方。我建议Vehicle 应该包含一个方法来指示它是否只能由牵引车、变速驱动器或... ...任何服务员。请注意,这种能力不一定取决于对象的“类型”;发动机抛出杆的汽车仍然是Car,但GetRequirementsForMotion() 会报告它需要牵引车。 @supercat:车库不应该关心服务员的驾驶能力——每当服务员出现新的障碍时,这只会导致一连串的变化。 :P 相反,由于服务员显然是一个具有自己属性的实体,所以它应该是一个对象。车库应告知服务员车辆已到,让服务员将车辆停放在车位(必要时移动其他车辆)。 @supercat:至于建议...实际上,我会提出比这更通用的建议——一种用特定属性标记车辆并查询它们的方法。您可以有表示“需要拖曳”、“变速杆”等的值。子类可以通过将属性映射到属性来响应查询——虽然有点意思,但至少可以将丑陋与调用者隔离开来。 (尽管走这条路可能完全不需要不同的CarBoat 等类型,具体取决于这些类还需要做什么。) @supercat 我说的很傻,可以忽略,但请查看 thatwhich。或者,简短的版本,开始只使用 that,除非它明显错误并且需要 which。您真正的公益公告!【参考方案3】:

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

我知道正确建模一个类来表示车库无助于回答您的“车库里的汽车”问题;只是一个建议。

回到代码。除了让 OOP 陷入困境之外,一些问题将有助于创建场景,从而更好地理解您想要解决的问题(假设有一个问题,而不仅仅是“陷入困境”):

    谁或什么想了解carIsAutomatic? 给定carIsAutomatic,谁或什么会执行doSomeCarStuff

可能是一些检查员,或者只知道如何驾驶自动变速器汽车的人等,但从车库的角度来看,它所知道的只是它拥有一些车辆,因此(在这个模型中)它是责任这位检查员或司机来判断它是汽车还是船;此时,您可能想要开始创建另一组类来表示场景中相似类型的 *actor*s。取决于要解决的问题,如果你真的需要,你可以把车库建模成一个超级智能系统,让它像自动售货机一样,而不是普通的车库,有一个按钮说“汽车”,另一个说“船”,人们可以随心所欲地按下按钮来获得汽车或船,这反过来又使这个超级智能车库负责告诉用户应该向用户展示什么(汽车或船);为了遵循这种即兴创作,车库在接收车辆时可能需要记账,可能需要有人提供信息等,所有这些职责都超出了简单的 Main 类。

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

【讨论】:

【参考方案4】:

这是应用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 接口中。

【讨论】:

【参考方案5】:

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

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

Java 是一种类型安全的语言,因此最好在处理已转换为 Boats 和 Cars 之类的数据之前始终进行类型检查。

【讨论】:

您的方法与多态性完全相反,除非没有其他合理的方法,否则您不应该这样做。在这个类的简单示例中,它看起来不错,但在一个 20.000 行的小项目中,它可能是一团糟。另一方面,您永远不应该拥有公共可变字段,因为这会破坏封装。最后,Car、Boat 和 MotorCycle 类并没有覆盖它们在 Vehicule 中的同名字段,它们只是遮蔽它们(或者它们会真正编译)。 @IgorRodriguez 在您提出一个好观点时取消了对字段的静音,但我无法理解这不是多态的。?车辆可以是汽车、船或摩托车。汽车是汽车,船是船,摩托车是摩托车,两者不能相互。话虽这么说,这与实现一堆instanceofs 不是一回事吗?例如MotorCycle instanceof Vehicle // trueMotorCycle instanceof Car // false 多态的诀窍在于,您不需要知道何时使用一辆车或一艘船。例如,如果您的 Vehicule 类中有一个 park() 方法,Car 将实现它,以便将汽车存储在 Garage 中,而 Boat 将在 Harbour 中实现。然后,当你有一个 Vehicules 集合时,一些是 Boats,一些 Cars 等等,每次你在其中任何一个中调用 park() 时,都会执行正确的操作,即使你不知道任何的类型是否他们是。这就是多态性:在你需要知道对象类型的那一刻,缺少它。【参考方案6】:

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

【讨论】:

应尽可能避免反思。有很多反对它的论据,例如:plumbr.eu/blog/six-java-features-to-stay-away-from【参考方案7】:

要回答您的问题,您可以执行以下操作来了解您的车库中到底有什么:

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

更新:正如您从下面的 cmets 中看到的,这种方法适用于简单的解决方案,但不是好的做法,尤其是当您的车库中有大量车辆时。所以只有当你知道车库会保持很小的时候才使用它。如果不是这样,在堆栈溢出时搜索“Avoiding instanceof”,有多种方法可以做到。

【讨论】:

这里有一个重要提示:如果你发现自己经常使用很多这样的instanceof,你真的应该退后一步,看看你是否可以用更多的方式重写你的代码多态方式。 @Keppil 你是绝对正确的,但也许它是为了演示目的而发布的,向 OP 展示他有哪些课程,这没关系。 @T-Rex:你已经从@Keppil 那里得到了答案——不,不是。如果可能,请避免使用instanceof。上面的代码很好的说明了这一点,但不常用。 在任何情况下,我都建议至少在最后加上else throw new UnsupportedVehicleError(v); ,除非你确定什么都不做才是处理任何问题的正确方法以及除汽车或船只以外的所有车辆。 @T-Rex 如果汽车doSomeCarStuff 和船doSomeBoatStuff,你不能说Vehicles 都是doSomeStuff 吗?你知道它是一种车辆,每种类型的车辆都会做一些事情(可能还有一些事情)。您不一定需要知道什么,Vehicle 可以解决这个问题。车库不在乎汽车开出和船被拖走,它只知道现在有一个空位。【参考方案8】:

我真的只是在这里汇集其他人的想法(而且我不是 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 到一个接口(例如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 可以检查公共汽车和汽车,但它只能询问变速箱。

现在,您可能会认为布尔值并不是您关心的全部。此时,您可能更喜欢使用通用的 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 人,所以上面的一些语法可能是错误的,但这些概念应该成立。不过,如果没有先测试,请不要在任何重要的地方运行上述代码。

【讨论】:

这个概念成立,但它与每个 OOP 原则都背道而驰。您需要查看访问者模式是如何实现的,这将消除对 InspectionBoolean 枚举的需要 - 这不是布尔值。 @Matt - 注意到 InspectionBoolean 的语义 - 我当时想不出更好的名字......【参考方案9】:

你问过你的管家:

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

懒惰的吉夫斯说:

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

就是这样。

好吧,这还不是全部,因为现实比静态类型更多的是鸭子类型。这就是为什么我说 Jeeves 很懒。

【讨论】:

【参考方案10】:

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

多态性的好处是能够在Vehicle 上调用getPriceAfterYears而无需关心实现。

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

注意:当然这里的设计可以很容易地改进。只是举例说明要点。

【讨论】:

那为什么会有多态性呢?谁说我们应该?我家的车肯定不知道值多少钱。那将是一些 CarMarketApp 或其他东西的任务。我想说的是价格可能不是最好的例子,也许我们应该使用组合而不是继承。 @T-Rex 绝对不是!在某些情况下,基类本身就很好,但您可能还想从它继承。继续以Vehicle 为例,您的车库中可能有一个Boat,其中包含操作船只所需的所有信息。但是你的邻居可能有一个SkiBoat,它可能还有一个滑雪绳长度的方法等等。 我看不出在这种情况下向下转换有什么问题。如果您的车库有这么多车位,并且每个车位都可以被汽车或船占据,那么 OP 的设计就非常合理。这里的设计带有单独的汽车和船只列表,这意味着每当您添加一种新型车辆(摩托车、拖车等)时,您都必须更新代码,这肯定比铸造更糟糕。 使用继承和多态的主要原因:你大多关心汽车和船的区别。 (如果这不是真的,那么继续使用单独的列表) @Gabe:public function canDrive(Vehicle vehicle) for (Skill skill : skills) if (vehicle.canBeDrivenWith(skill)) return true; return false; 。现在只有车辆需要知道它的传输。现在,您的皮卡车代客可以将牵引列为一项技能,而您不必完全做其他事情 - 到时候,他们只需在拖车/船,而不是尝试启动和驾驶它。【参考方案11】:

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

如果您想访问扩展类型,但有一个存储它的基本类型的字段(如您的情况),您首先必须转换它,然后才能访问它:

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

或者没有临时字段的更短:

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

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

【讨论】:

为简单起见,我将覆盖每个类中的 ToString() 方法并将具体类型作为字符串返回,然后使用 switch() 来决定如何处理每个项目。 @JuannStrauss:我反对 - 因为 toString() 不适用于该用例,所以 switch 将是一个脆弱的构造,因为它意味着期望每个子类也这样做。 【参考方案12】:

您定义了您的车库将存放车辆,因此您不必关心您拥有什么类型的车辆。这些车辆具有共同的特征,如发动机、车轮、移动等行为。 这些特征的实际表示可能不同,但在抽象层是相同的。 您使用了抽象类,这意味着两辆车的某些属性、行为完全相同。如果您想表达您的车辆具有共同的抽象特征,那么使用像移动这样的界面可能意味着汽车和船的不同。两者都可以从 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,船可能使用声纳来识别它的位置。

【讨论】:

谢谢。准确地说,我没有详细说明我在这里所做的全部练习。 Vehicle 有一个抽象方法 steer(),它在 CarBoat 中的实现方式不同。它运作良好。 Vehicle 还实现了moveable 并实现了move() 方法,该方法适用于CarBoat。另一个问题对我来说是:使用抽象与使用接口之间有什么区别 - 除了我可以拥有一个在可移动对象周围移动的独立于类的方法这一事实之外? 哦,太好了,我不知道抽象可以这样工作。非常感谢! @T-Rex 某些语言,如 Java,将您限制为单个超类(在本例中为抽象父类),而您可以在单个子类中实现多个接口。【参考方案13】:

你的车库包含车辆,所以编译器静态控制视图你有一个车辆,因为 .auto 是一个 Car 字段,你不能访问它,动态它是一个 Car 所以演员不会产生一些问题,如果这将是一艘船,而您尝试对 Car 进行强制转换会在运行时引发异常。

【讨论】:

以上是关于我的车库里真的有车吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

科普2:用车库出租讲解如何实现分布式存储—复制证明和时空证明

修改网络表单模块

从 uitableview 隐藏或删除部分

QObject 真的有多重? [复制]

Python 的 copy.deepcopy 真的会复制所有内容吗? [复制]

最佳实践:sql 视图真的值得吗? [复制]