我的车库里真的有车吗?
我是Java编程的新手,试图获得OOP的悬念。
所以我build立了这个抽象类:
public abstract class Vehicle{....}
和2个子类:
public class Car extends Vehicle{....} public class Boat extends Vehicle{....}
Car
和Boat
还拥有一些不常见的独特的领域和方法(不具有相同的名称,所以我不能在车辆中定义它们的抽象方法)。
现在在mainClass中,我已经安装了新的Garage:
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
- 作为静态types语言,如果通过基于Vehicle超类的数据结构(如
Vehicle myGarage[]
)访问这些语言,Java编译器将不会访问非“Vehicle”的方法/字段 - 至于如何解决,主要有两种方法:
- 使用types转换,这将缓解编译器的问题,并在devise中留下任何错误来运行时间
- 我需要铸造的事实说,devise是有缺陷的。 如果我需要访问非车辆function,那么我不应该将汽车和船存储在基于车辆的数据结构中。 要么使所有这些function属于Vehicle,要么使用更具体的(派生的)基于types的结构
- 在许多情况下,组合和/或接口将是inheritance的更好select。 可能是我下一个问题的主题…
- 再加上其他许多好的见解,如果有人有时间浏览答案。
如果您需要在您的车库中区分Car
和Boat
,那么您应该将它们存储在不同的结构中。
例如:
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年后价格的公式取决于车辆,所以留给实施class来定义。 例如:
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
而不关心实现。
通常情况下,下倾是一个有缺陷的devise的标志:如果你需要区分他们的实际types,不要把你的车辆一起存放。
注意:这里的devise当然可以很容易地改进。 这只是一个例子来certificate这一点。
要回答你的问题,你可以找出你的车库到底是什么,你做了以下几点:
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(); }
更新:正如你可以阅读下面的评论,这种方法可以简单的解决scheme,但它不是一个好的做法,特别是如果你有一个车库的车辆数量巨大。 所以只有当你知道车库会保持小型时才使用它。 如果不是这种情况,请在堆栈溢出中search“避免instanceof”,有多种方法可以实现。
如果您使用基本types,则只能访问公共方法和字段。
如果你想访问扩展types,但是有一个存储基types的字段(就像你的情况一样),你首先必须将其转换,然后你可以访问它:
Car car = (Car)myGarage[0]; car.doSomeCarStuff();
或没有临时领域更短:
((Car)myGarage[0]).doSomeCarStuff();
由于您正在使用Vehicle
对象,因此只能从基类中调用方法而不投射。 因此,对于你的车库来说,区分不同arrays中的对象或更好的列表可能是明智的,因为数组通常不是一个好主意,因为它比基于Collection
的类更不灵活。
你定义你的车库将存储车辆,所以你不关心你有什么types的车辆。 车辆具有发动机,车轮,移动等行为的共同特征。 这些function的实际表示可能不同,但在抽象层是相同的。 你使用了抽象类,这意味着两个车辆的一些属性,行为是完全一样的。 如果你想expression你的车具有共同的抽象特征,那么使用界面就像移动可能意味着不同的汽车和船。 两者都可以从A点到B点,但是以不同的方式(在车轮上或在水面上 – 因此实施将会不同)。因此,您在车库中的车辆行为方式相同,而且不会针对特定function他们。
回答评论:
界面是指描述如何与外部世界进行交stream的合同。 在合同中,你定义了你的车辆可以移动,可以被操纵,但是你没有描述它是如何实际工作的,这在实现中有描述。通过抽象类你可能有一些function让你分享一些实现,但是你也有函数,你不知道它将如何实现。
使用抽象类的一个例子:
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(); } }
您将使用每辆车的相同步骤,但步骤的实施将因车型而异。 汽车可能使用全球定位系统,船可能会使用声纳来确定它在哪里。
我是Java编程的新手,试图获得OOP的悬念。
只是我的2美分 – 我会尽量减less很多有趣的事情已经说了。 但事实上,这里有两个问题。 一个关于“OOP”和一个关于如何在Java中实现的。
首先,是的,你的车库里有一辆车。 所以你的假设是正确的。 但是,Java是一种静态types的语言。 编译器中的types系统只能通过相应的声明 “知道”各种对象的types。 不是由他们的使用。 如果你有一个Vehicle
数组,编译器只知道这个。 所以它会检查你是否只执行任何 Vehicle
上允许的操作。 (换句话说,在Vehicle
声明中可见的方法和属性 )。
通过使用明确的演员(Car)
,你可以向编译器解释“你实际上知道这Vehicle
是一辆Car
” 。 编译器会相信你 – 即使在Java中有一个在运行时检查,这可能会导致一个ClassCastException
,以防止进一步的损害,如果你撒谎 (其他语言像C + +不会在运行时检查 – 你必须知道你在做什么)
最后,如果你确实需要,你可能会依赖运行时types标识(即: instanceof
)来检查对象的“真实”types,然后再尝试进行强制转换。 但是这在Java中被认为是不好的做法。
正如我所说的,这是实现面向对象的Java方式。 有完全不同的 类 被广泛称为“dynamic语言”的语言家族 , 只在运行时才检查对象是否允许操作。 使用这些语言,您不需要将所有常用方法“上移”到某些(可能是抽象的)基类以满足types系统。 这被称为鸭子打字 。
你问你的pipe家:
Jeeves,还记得我在爪哇岛的车库吗? 检查停放的第一辆车是否自动。
懒惰的Jeeves说:
但先生,如果这是一个不能自动或非自动的车辆?
就这样。
好吧,这并不是全部,因为现实比静态types更加鸭式。 这就是为什么我说Jeeves是懒惰的
你的问题在一个更基础的层面上:你build立的Vehicle
的方式, Garage
需要知道更多的关于它的对象比Vehicle
接口给出。 你应该尝试从Garage
angular度来build立Vehicle
类(一般来说,从所有将要使用Vehicle
的angular度来看):他们需要做什么样的事情来处理他们的车辆? 我如何用我的方法使这些事情成为可能?
例如,从你的例子:
bool carIsAutomatic = myGarage[0].auto;
你的车库想知道一辆车的发动机的原因是什么? 无论如何,这是没有必要只是由Car
曝光。 您仍然可以在Vehicle
公开一个未实现的isAutomatic()
方法,然后将其实现为return True
in Boat
, return this.auto
在Car
return this.auto
。
如果有一个三值EngineType
枚举( HAS_NO_GEARS
, HAS_GEARS_AUTO_SHIFT
, HAS_GEARS_MANUAL_SHIFT
)会HAS_GEARS_MANUAL_SHIFT
,这会让你的代码干净而准确地HAS_GEARS_MANUAL_SHIFT
出通用Vehicle
的实际特性。 (无论如何,你需要这个区别来处理摩托车。)
你的车库中包含车辆,所以编译器的静态控制视图,你有一个车辆和.auto是汽车领域,你不能访问它,dynamic它是一个汽车,所以铸造不会产生一些问题,如果它会一艘船,你试图投到汽车将在运行时上升。
这是Visitor
devise模式应用的好地方。
这种模式的优点是你可以调用不相关的代码在一个超类的不同的子类,而不必做任何奇怪的转换,或将大量不相关的方法放入超类。
这通过创build一个Visitor
对象并允许我们的Vehicle
类accept()
访问者。
你也可以使用相同的方法创build许多types的Visitor
并调用不相关的代码,只是一个不同的Visitor
实现,这使得这个devise模式在创build干净的类时非常强大。
演示例如:
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
上的不同代码,这取决于调用哪个visit
实现。 您也可以创buildVisitor的其他实现,以相同的.visit()
模式调用不同的代码。
另请注意,使用这种方法,没有使用instanceof
或任何hacky类检查。 类之间唯一重复的代码是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
抽象为一个接口(例如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; } }
现在你在说,你只是关于变速器,不pipe车辆,所以可以问TransmissionInspector。 公交车和汽车都可以由TransmissionInspector检查,但只能询问变速箱。
现在,你可能会决定布尔值不是你所关心的。 在这一点上,你可能更喜欢使用一个通用的支持types,它公开了支持的状态和值:
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的人,所以上面的一些语法可能是错误的,但概念应该保持。 不过,不要在没有先testing的情况下运行上述任何重要的东西。
如果您使用的是Java,则可以使用reflection来检查函数是否可用并执行它
创build车辆级别字段,这将有助于使每个单独的车辆更清晰。
public abstract class Vehicle { public final boolean isCar; public final boolean isBoat; public Vehicle (boolean isCar, boolean isBoat) { this.isCar = isCar; this.isBoat = isBoat; } }
将inheritance类中的Vehicle级别字段设置为适当的值。
public class Car extends Vehicle { public Car (...) { super(true, false); ... } } public class Boat extends Vehicle { public Boat (...) { super(false, true); ... } }
使用车辆级别字段来正确解读车辆types。
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(); }
既然你告诉你的编译器,你的车库里的所有东西都是一辆车,那么你的汽车级别的方法和领域就会停滞不前。 如果你想正确解译车辆types,那么你应该设置一些类级别的字段,例如isCar
和isBoat
,这将给你程序员更好地了解你正在使用什么types的车辆。
Java是一种types安全的语言,所以最好在处理像Boat
和Car
这样的数据之前进行types检查。
为了解决某些问题,将要呈现在程序中的对象build模是一回事,编码是另一回事。 在你的代码中,我认为使用数组build模车库本质上是不合适的。 Arrays shouldn't be often considered as objects, although they do appear to be, usually for the sake of self-contained-ness sort of integrity of a language and providing some familiarity, but array as a type is really just a computer-specific thing, IMHO, especially in Java, where you can't extend arrays.
I understand that correctly modeling a class to represent a garage won't help answer your "cars in a garage" question; just a piece of advice.
Head back to the code. Other than getting some hang to OOP, a few questions would be helpful creating a scene hence to better understand the problem you want to resolve (assuming there is one, not just "getting some hang"):
- Who or what wants to understand
carIsAutomatic
? - Given
carIsAutomatic
, who or what would performdoSomeCarStuff
?
It might be some inspector, or someone who knows only how to drive auto-transmission cars, etc., but from the garage's perspective, all it knows is it holds some vehicle, therefore (in this model) it is the responsibility of this inspector or driver to tell if it's a car or a boat; at this moment, you may want to start creating another bunch of classes to represent similar types of *actor*s in the scene. Depends on the problem to be resolved, if you really have to, you can model the garage to be a super intelligent system so it behaves like a vending machine, instead of a regular garage, that has a button says "Car" and another says "Boat", so that people can push the button to get a car or a boat as they want, which in turn makes this super intelligent garage responsible for telling what (a car or a boat) should be presented to its users; to follow this improvisation, the garage may require some bookkeeping when it accepts a vehicle, someone may have to provide the information, etc., all these responsibilities go beyond a simple Main class.
Having said this much, certainly I understand all the troubles, along with the boilerplates, to code an OO program, especially when the problem it tries to resolve is very simple, but OO is indeed a feasible way to resolve many other problems. From my experience, with some input providing use cases, people start to design scenes how objects would interact with each other, categorize them into classes (as well as interfaces in Java), then use something like your Main class to bootstrap the world .