这个问题在这里已有答案:
我是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;
编译器不接受 . 我使用cast来解决这个问题:
boolean carIsAutomatic = ((Car)myGarage[0]).auto;
这有效......但它对方法没有帮助,只是字段 . 意思是我做不到
(Car)myGarage[0].doSomeCarStuff();
所以我的问题是 - 我的车库里到底有什么?我试图获得直觉以及了解“幕后”的情况 .
为了未来的读者,请简要总结以下答案:
-
是的,
myGarage[]
中有一个Car
-
作为静态类型语言,如果通过基于Vehicle超类的数据结构(例如
Vehicle myGarage[]
)访问那些方法/字段,Java编译器将不会访问非"Vehicle"的方法/字段 . -
至于如何解决,有以下两种主要方法:
-
使用类型转换,这将减轻编译器的顾虑,并将设计中的任何错误留给运行时
-
我需要铸造的事实说设计是有缺陷的 . 如果我需要访问非车辆功能,那么我不应该将汽车和船只存储在基于车辆的数据结构中 . 要么使所有这些功能属于Vehicle,要么使用更具体(派生)类型的结构
-
在许多情况下,组合和/或接口将是继承的更好替代方案 . 可能是我下一个问题的主题......
-
如果有人有时间浏览答案,还有很多其他好的见解 .
13 回答
如果您需要在车库中区分
Car
和Boat
,那么您应该将它们存储在不同的结构中 .例如:
然后,您可以定义特定于船只或特定于汽车的方法 .
为什么会有多态?
让我们说
Vehicle
就像:每个
Vehicle
都有一个价格,所以它可以放在Vehicle
抽象类中 .然而,确定n年后价格的公式取决于车辆,因此由实施小组来定义它 . 例如:
Boat
类可能具有getPriceAfterYears
的其他定义以及特定属性和方法 .所以现在回到
Garage
类,您可以定义:多态的兴趣是能够在关注实现的情况下调用
getPriceAfterYears
.通常,向下转向是设计有缺陷的标志:如果您需要区分其实际类型,请不要将您的车辆全部存放在一起 .
注意:当然这里的设计很容易改进 . 这只是一个证明要点的例子 .
要回答您的问题,您可以了解您的车库究竟是什么,您可以执行以下操作:
更新:正如你可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但这不是一个好的做法,特别是如果你的车库里有大量的车辆 . 所以只有当你知道车库会很小的时候才使用它 . 如果不是这种情况,请在堆栈溢出时搜索“避免instanceof”,有多种方法可以执行此操作 .
如果您对基本类型进行操作,则只能访问它的公共方法和字段 .
如果你想访问扩展类型,但是有一个存储它的基本类型的字段(如你的情况),你首先必须强制转换它然后你可以访问它:
或者没有临时场的时间更短
由于您使用的是
Vehicle
对象,因此只能在其上调用基类中的方法而不进行强制转换 . 因此,对于你的车库,建议区分不同阵列中的对象 - 或更好的列表 - 数组通常不是一个好主意,因为它在处理方面远不如基于Collection
的类 .您定义了您的车库将存储车辆,因此您不关心您拥有什么类型的车辆 . 这些车辆具有发动机,车轮,移动等行为等共同特征 . 这些特征的实际表示可能不同,但在抽象层是相同的 . 你使用了抽象类,这意味着两个属性,行为完全相同 . 如果你想表达你的车辆有共同的抽象功能,那么使用移动等界面可能意味着汽车和船只的不同 . 两者都可以从A点到达B点,但是以不同的方式(在轮子上或在水上 - 所以实施方式会有所不同)所以你在车库里有车辆的行为方式相同但你不关心具体的功能的他们 .
回答评论:
接口是指描述如何与外部世界通信的 Contract . 在 Contract 中,您定义您的车辆可以移动,可以操纵,但是您没有描述它将如何实际工作,它在实现中描述 . 通过抽象类,您可能具有共享某些实现的功能,但您也有你不知道它将如何实现的功能 .
使用抽象类的一个例子:
您将使用每辆车的相同步骤,但步骤的实施因车辆类型而异 . 汽车可能会使用GPS,船可能会使用声纳识别它的位置 .
仅仅是我的2美分 - 我会尝试缩短它,因为已经说过许多有趣的事情 . 但事实上,这里有两个问题 . 一个关于“OOP”,一个关于如何在Java中实现它 .
首先,是的,你的车库里有一辆车 . 所以你的假设是正确的 . 但是,Java是一种 statically typed 语言 . 并且编译器中的类型系统只能"know"通过相应的声明来表示各种对象的类型 . 不是他们的用法 . 如果你有一个
Vehicle
数组,编译器只知道它 . 因此,它将检查您是否仅执行任何Vehicle
上允许的操作 . (换句话说,Vehicle
声明中可见的方法和属性) .你可以通过使用显式转换
(Car)
向编译器解释“你实际上知道这个Vehicle
是Car
” . 编译器会相信你 - 即使在Java中运行时有一个检查,这可能导致ClassCastException
阻止进一步损害如果你撒谎(其他语言如C不会在运行时检查 - 你必须知道你做了什么)最后,如果您确实需要,可以依赖运行时类型标识(即:
instanceof
)来检查对象的"real"类型,然后再尝试强制转换它 . 但这在Java中被认为是一种不好的做法 .正如我所说,这是实现OOP的Java方式 . 有一个完整的不同类语言系列,通常称为"dynamic languages",它只在运行时检查是否允许对象进行操作 . 使用这些语言,您不需要"move up"一些(可能是抽象的)基类的所有常用方法来满足类型系统 . 这叫做duck typing .
你问过你的管家:
和懒惰的Jeeves说:
就这样 .
好吧,这并不是全部,因为现实比静态类型更像鸭子类型 . 这就是我说Jeeves很懒的原因 .
这里你的问题处于一个更基础的层面:你 Build 了
Vehicle
,以便Garage
需要了解更多关于它的对象,而不是Vehicle
接口 . 您应该尝试从Garage
角度构建Vehicle
类(通常从所有将要使用的东西的角度来看Vehicle
):他们需要对他们的车辆做什么样的事情?如何用我的方法使这些事情成为可能?例如,从您的示例:
你的车库想知道车辆发动机的原因是什么?无论如何,
Car
没有必要公开这个 . 您仍然可以在Vehicle
中公开未实现的isAutomatic()
方法,然后在Boat
中将return True
实现为Car
中的return this.auto
.拥有一个三值的
EngineType
枚举(HAS_NO_GEARS
,HAS_GEARS_AUTO_SHIFT
,HAS_GEARS_MANUAL_SHIFT
)会更好,它会让你的代码干净准确地推断出通用Vehicle
的实际特征 . (无论如何,你需要这种区别来处理摩托车 . )你的车库包含车辆,所以编译器静态控制视图你有一个车辆和.auto是一个你无法访问它的Car字段,动态它是一个Car所以演员不会产生一些问题,如果它将是一艘船,你试图让施放到Car将在运行时引发异常 .
这是应用
Visitor
设计模式的好地方 .这种模式的优点是你可以在超类的不同子类上调用不相关的代码,而不必在任何地方进行奇怪的转换或将大量不相关的方法放入超类中 .
这可以通过创建一个
Visitor
对象并允许我们的Vehicle
类来访问accept()
来实现 .您还可以使用相同的方法创建多种类型的
Visitor
并调用不相关的代码,只是一个不同的Visitor
实现,这使得这种设计模式在创建干净的类时非常强大 .一个演示例如:
如您所见,
StuffVisitor
允许您在Boat
或Car
上调用不同的代码,具体取决于调用visit
的实现 . 您还可以创建Visitor的其他实现来调用不同的代码相同的.visit()
模式 .另请注意,使用此方法时,不会使用
instanceof
或任何hacky类检查 . 类之间唯一重复的代码是方法void accept(Visitor)
.例如,如果要支持3种类型的具体子类,也可以将该实现添加到
Visitor
接口中 .我真的只是在这里汇集其他人的想法(我不是一个Java人,所以这是假的而不是实际的)但是,在这个人为的例子中,我会将我的汽车检查方法抽象为一个专门的类,在看车库时,只知道汽车,只关心汽车:
重点是,当你询问汽车的变速器时,你已经决定只关心汽车了 . 所以请问CarInspector . 感谢三国的Enum,您现在可以知道它是自动的还是不是汽车 .
当然,对于您关心的每辆车,您都需要不同的VehicleInspectors . 而你刚刚推出了哪个VehicleInspector实例化链的问题 .
因此,您可能希望查看接口 .
摘要
getTransmission
输出到接口(例如HasTransmission
) . 这样,您可以检查车辆是否有传输,或者写一个TransmissionInspector:现在你说,你只关于传输,不管车辆,所以可以问TransmissionInspector . 总线和汽车都可以通过TransmissionInspector进行检查,但它只能询问传输情况 .
现在,您可能会认为布尔值并非您所关心的全部 . 此时,您可能更喜欢使用通用的Supported类型,它公开支持的状态和值:
现在您的Inspector可能被定义为:
正如我所说,我不是Java人,所以上面的一些语法可能是错误的,但概念应该成立 . 尽管如此,如果没有先测试,请不要将上述内容放在任何重要位
如果您使用Java,可以使用反射来检查函数是否可用并执行它
Create 车辆级别字段,有助于使每个车辆更加独特 .
Set 继承类中的Vehicle级别字段为适当的值 .
Implement 使用车辆级别字段正确解读车辆类型 .
由于您告诉编译器车库中的所有内容都是车辆,因此您坚持使用Vehicle类级方法和字段 . 如果要正确解密Vehicle类型,则应设置一些类级别字段,例如
isCar
和isBoat
将使您的程序员更好地了解您正在使用的车辆类型 .Java是一种类型安全的语言,因此在处理像
Boat
和Car
一样的数据之前,最好始终键入check .建模你想要在程序中呈现的对象(为了解决一些问题)是一回事,编码是另一回事 . 在你的代码中,我认为它本质上通常被认为是对象,尽管它们看起来确实如此,通常是出于自包含的语言完整性并提供一些熟悉性,但数组作为一种类型是真的只是一个特定于计算机的东西,恕我直言,特别是在Java中,你无法扩展数组 .
我明白正确建模一个类来代表一个车库将无法回答你的“车库中的汽车”问题;只是一条建议 .
回到代码 . 除了对OOP有所了解之外,一些问题将有助于创建一个场景,从而更好地理解您想要解决的问题(假设有一个,而不仅仅是“获得一些挂起”):
谁或想要了解
carIsAutomatic
?给定
carIsAutomatic
,谁或什么表演doSomeCarStuff
?可能是一些检查员,或者只知道如何驾驶汽车等车辆的人,但是从车库或船上开车;此时,您可能想要开始创建另一组类来表示场景中类似类型的* actor * s . 取决于要解决的问题,如果你真的需要,你可以把车库建模为一个超级智能系统,所以它的行为就像一个自动售货机,而不是一个普通的车库,有一个按钮说"Car"和另一个说"Boat",因此,人们可以按下按钮来获得他们想要的汽车或船只,这反过来使这个超级智能车库负责告诉应该向用户提供什么(汽车或船);为了跟随这种即兴创作,车库在接受车辆时可能需要一些簿记,有人可能需要提供信息等,所有这些责任都超出了简单的主类 .
说了这么多,当然我理解所有麻烦,以及样板,编写一个OO程序,特别是当它试图解决的问题非常简单时,OO确实是解决许多其他问题的可行方法 . 根据我的经验,通过一些提供用例的输入,人们开始设计场景如何相互交互,将它们分类为类(以及Java中的接口),然后使用像Main类这样的东西来引导世界 .