Do I really have a car in my garage?
我是一个Java编程新手,试图获得OOP的诀窍。
所以我创建了这个抽象类:
1 | public abstract class Vehicle{....} |
和2个子类:
1 2 | public class Car extends Vehicle{....} public class Boat extends Vehicle{....} |
现在我在Mainclass设置了我的新车库:
1 2 3 | Vehicle[] myGarage= new Vehicle[10]; myGarage[0]=new Car(2,true); myGarage[1]=new Boat(4,600); |
我对多态性非常满意,直到我尝试访问汽车特有的领域之一,例如:
1 | boolean carIsAutomatic = myGarage[0].auto; |
编译器不接受。我使用Casting解决了这个问题:
1 | boolean carIsAutomatic = ((Car)myGarage[0]).auto; |
那是有效的…但它对方法没有帮助,只是字段。意思是我做不到
1 | (Car)myGarage[0].doSomeCarStuff(); |
所以我的问题是——我的车库里到底有什么?我试着获得直觉,并理解"幕后"发生了什么。
为了将来的读者,以下是答案的简短总结:
如果您需要在车库中区分
例如:
1 2 3 4 | public class Garage { private List<Car> cars; private List<Boat> boats; } |
然后,您可以定义特定于船只或特定于汽车的方法。
为什么有多态性呢?假设
1 2 3 4 5 | public abstract class Vehicle { protected int price; public getPrice() { return price; } public abstract int getPriceAfterYears(int years); } |
每个
然而,决定n年后价格的公式取决于车辆,因此它留给实现类来定义它。例如:
1 2 3 4 5 6 7 8 9 | 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)); } } |
所以现在回到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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(); } |
多态性的好处是能够在不关心实现的情况下调用
通常,下压铸件是一个有缺陷的设计的标志:如果你需要区分车辆的实际类型,不要把你的车辆放在一起。
注:当然,这里的设计很容易改进。这只是一个例子来说明这些要点。
要回答您的问题,您可以了解车库中的具体内容,请执行以下操作:
1 2 3 4 5 6 7 8 9 | 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",有多种方法可以做到这一点。
如果在基类型上操作,则只能访问其公共方法和字段。
如果要访问扩展类型,但有一个存储它的基类型字段(如您的示例所示),则必须先强制转换它,然后才能访问它:
1 2 | Car car = (Car)myGarage[0]; car.doSomeCarStuff(); |
或更短,无温度场:
1 | ((Car)myGarage[0]).doSomeCarStuff(); |
由于您使用的是
I'm a newbie to Java programming, trying to get the hang of OOP.
只要我的2美分-我会尽量缩短,因为很多有趣的事情已经说了。但事实上,这里有两个问题。一个关于"OOP"和一个关于它是如何在Java中实现的。
首先,是的,你的车库里有辆车。所以你的假设是正确的。但是,Java是一种静态类型的语言。编译器中的类型系统只能通过相应的声明"知道"各种对象的类型。不是他们的习惯。如果您有一个
通过使用显式强制转换
最后,如果您确实需要的话,您可能需要依赖运行时类型标识(即:
正如我所说的,这是Java实现OOP的方式。有完全不同的语言系列,通常称为"动态语言",仅在运行时检查对象上是否允许操作。使用这些语言,您不需要将所有常用方法"上移"到某个(可能是抽象的)基类以满足类型系统。这叫鸭子打字。
您定义了您的车库将存储车辆,因此您不关心您拥有的车辆类型。汽车有发动机、车轮、运动等共同特征。这些特性的实际表示可能不同,但在抽象层上是相同的。您使用了抽象类,这意味着两辆车的某些属性和行为完全相同。如果你想表达你的车辆有共同的抽象特征,那么像移动这样的界面可能意味着汽车和船的不同。两者都可以从A点到B点,但以不同的方式(在车轮上或水上-因此实施将有所不同)所以车库里的车辆也有相同的行为方式,你不会因为它们的特定特征而开车。
回答意见:
接口是指描述如何与外部世界通信的合同。在合同中,您定义了您的车辆可以移动、可以转向,但您没有描述它实际的工作方式,而是在实现中进行了描述。通过抽象类,您可能拥有共享一些实现的函数,但您也拥有不知道如何实现的函数。
使用抽象类的一个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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,船可能使用声纳来识别它在哪里。
你问你的管家:
Jeeves, remember my garage on the Isle of Java? Go check whether the first vehicle parked there is automatic.
懒惰的吉维斯说:
but sir, what if it's a vehicle that can't be automatic or non-automatic?
这就是全部。
ok,这并不是全部,因为现实比静态类型更倾向于duck类型。这就是我为什么说吉维斯懒惰的原因。
这里的问题在一个更基本的层面上:您构建
例如,从您的示例中:
1 | bool carIsAutomatic = myGarage[0].auto; |
你的车库想知道一辆车的引擎…原因?不管怎么说,没有必要只让
如果有一个三值的
这是应用
这种模式的好处是,您可以在一个超类的不同子类上调用不相关的代码,而不必到处执行奇怪的强制转换,也不必将大量不相关的方法放入超类中。
这是通过创建一个
您还可以创建许多类型的
例如,演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | 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); } } } |
如您所见,
还请注意,使用此方法时,没有使用
例如,如果您想支持3种类型的具体子类,您也可以将该实现添加到
你的车库里有车辆,所以编译器静态控制视图显示你有一辆车和一辆车。汽车是一个汽车领域,你不能访问它,动态它是一辆汽车,所以演员不会造成一些问题,如果它将是一艘船,你试图使演员对汽车运行时会上升一个例外。
我真的只是在这里汇集其他人的想法(我不是爪哇人,所以这是假的,而不是实际的),但是,在这个设计的例子中,我将我的汽车检查方法抽象成一个专用的类,它只知道汽车,只关心车库时的汽车:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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就行了。多亏了三态枚举,您现在可以知道它是自动的还是非汽车的。
当然,对于您关心的每辆车,您需要不同的车辆检查员。你刚刚把问题推到了哪辆车的检测者来例示这个链条上。
因此,您可能需要查看接口。
将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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; } } |
现在你说的是,你只关心变速器,不管车辆如何,所以可以问变速器检测仪。汽车和公共汽车都可以由变速器检测仪检测,但只能询问有关变速器的情况。
现在,您可能决定布尔值不是您所关心的全部。此时,您可能更喜欢使用通用的受支持类型,它公开受支持的状态和值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; } } |
现在,您的检查员可以定义为:
1 2 3 4 5 6 7 8 9 10 11 | 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的家伙,所以上面的一些语法可能是错误的,但是概念应该成立。不过,在没有首先测试的情况下,不要在任何重要的地方运行上面的代码。
如果您使用Java,可以使用反射来检查函数是否可用并执行它。
创建车辆级别字段,有助于使每个单独的车辆更加独特。
1 2 3 4 5 6 7 8 9 | public abstract class Vehicle { public final boolean isCar; public final boolean isBoat; public Vehicle (boolean isCar, boolean isBoat) { this.isCar = isCar; this.isBoat = isBoat; } } |
将继承类中的车辆级别字段设置为适当的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class Car extends Vehicle { public Car (...) { super(true, false); ... } } public class Boat extends Vehicle { public Boat (...) { super(false, true); ... } } |
使用"Vehicle Level(车辆级别)"字段正确地解密车辆类型。
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); } |
由于您告诉编译器车库中的所有东西都是一辆车,所以您一直坚持使用车辆类级别的方法和字段。如果您想正确地破译车辆类型,那么您应该设置一些类级字段,例如
Java是一种类型安全的语言,因此最好在处理像EDCOX1、4和S和EDCOX1×5 s这样的数据之前总是键入检查。
要在程序中呈现的建模对象(为了解决某些问题)是一回事,编码是另一回事。在您的代码中,我认为使用数组对车库建模本质上是不合适的。数组不应该被视为对象,尽管它们确实是,通常是为了自给自足的语言的完整性,提供一些熟悉性,但是数组作为一种类型实际上只是一个计算机特定的东西,特别是在Java中,你不能扩展数组。
我知道,正确地模拟一个班级来代表一个车库不会有助于回答你的"车库里的汽车"问题,只是一条建议。
回到代码。除了获得一些挂起的OOP,一些问题将有助于创建一个场景,从而更好地理解您想要解决的问题(假设有一个问题,而不仅仅是"获得一些挂起"):
可能是一些检查员,或者只知道如何驾驶自动变速器汽车的人,等等,但是从车库的角度来看,它只知道它拥有一些车辆,因此(在这个模型中),这个检查员或司机有责任告诉它是一辆车还是一艘船;此时此刻,你可能想开始创建另一组cl。表示场景中类似类型的*actor*s。取决于要解决的问题,如果你真的必须这样做,你可以把车库模型化为一个超级智能系统,这样它就像一台自动售货机,而不是一个普通的车库,有一个按钮说"车",另一个按钮说"船",这样人们就可以按这个按钮得到他们想要的车或船,这反过来又使得这个超级Intel负责告诉用户(汽车或船)应该呈现什么内容的疏忽车库;为了遵循这种即兴创作,车库在接受车辆时可能需要一些簿记,可能需要有人提供信息等,所有这些责任都超出了简单的主要类。
说了这么多,我当然理解所有的问题,连同样板文件,编写一个OO程序,特别是当它试图解决的问题非常简单时,但是OO确实是解决许多其他问题的可行方法。根据我的经验,在一些输入提供用例的情况下,人们开始设计场景,对象之间如何相互作用,将它们分类为类(以及Java中的接口),然后使用像主类之类的东西来引导世界。