面向对象设计问题
在这个简单的例子中,什么是好的设计:
假设我有一个基类 Car,其方法为 FillTank(Fueluel)
,其中 燃料也是一个基类,它有几个叶类,柴油,乙醇等。
在我的叶车类 DieselCar.FillTank(Fuelfuel)
上,只有某种类型的燃料 是允许的(这并不奇怪:))。现在我担心的是,根据我的界面,每辆车都可以加任何燃料,但这对我来说似乎是错误的,在每个 FillTank()
实现中检查输入燃料的类型是否正确,如果不正确抛出错误什么的。
我怎样才能将这种情况重新设计为更准确的情况,有可能吗? 如何设计一个以基类作为输入的基方法,而不会得到这些“奇怪的结果”?
What is good design in this simple case:
Let's say I have a base class Car with a method FillTank(Fuel fuel)
where
fuel is also a base class which have several leaf classes, diesel, ethanol etc.
On my leaf car class DieselCar.FillTank(Fuel fuel)
only a certain type of fuel
is allowed (no surprises there:)). Now here is my concern, according to my interface every car can be tanked with any fuel, but that seems wrong to me, in every FillTank()
implementation check the input fuel for the correct type and if not throw error or something.
How can I redesign such case to a more accurate one, is it even possible?
How to design a base method which takes a base-class for input without getting these "strange results"?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
使用通用基类(如果您的语言支持它(下面是 C#)):
基本上,这会强制从 car 继承的任何类指定它使用的燃料类型。此外,
Car
类强加了一个限制,即TFuel
必须是抽象Fuel
类的某种子类型。假设我们有一些简单的 Diesel 类:
以及一辆仅使用柴油运行的汽车:
Use a generic base class (if your language supports it (the below is C#)):
Basically this enforces any class that inherits from car to specify which type of fuel it uses. Furthermore, the
Car
class imposes a restriction thatTFuel
must be some subtype of the abstractFuel
class.Lets say we have some class
Diesel
which is simple:And a car which only runs on diesel:
仅靠面向对象编程并不能很好地解决这个问题。您需要的是通用编程(此处显示的 C++ 解决方案):
您的柴油车只是一辆特定的汽车,
Car
。Object-oriented programming alone cannot handle this problem well. What you need is generic programming (C++ solution shown here):
Your diesel car is then just a specific car,
Car<Diesel>
.如果汽车类型和燃料类型之间存在硬性界限,那么
FillTank()
就没有必要存在于Car
基类中,因为知道您拥有一辆车没有告诉你哪种燃料。因此,为了确保编译时的正确性,应在子类中定义FillTank()
,并且应仅采用该子类的Fuel
子类作品。但是,如果您不想在子类之间重复使用公共代码怎么办?然后,为子类的函数调用的基类编写一个protected
FillingTank()
方法。Fuel
也是如此。但是,如果您有一辆神奇的汽车,可以使用多种燃料(例如柴油或汽油)运行,该怎么办?然后,该汽车将成为
DieselCar
和GasCar
的子类,并且您需要确保将Car
声明为虚拟超类,这样您就不会DualFuelCar
对象中有两个Car
实例。加注油箱应该只需很少的修改或无需修改即可工作:默认情况下,您将同时获得DualFuelCar.FillTank(GasFuel)
和DualFuelCar.FillTank(DieselFuel)
,为您提供按类型重载的函数。但是,如果您不希望子类具有
FillTank()
函数怎么办?然后您需要切换到运行时检查并执行您认为必须执行的操作:使子类检查Fuel.type
并抛出异常或返回错误代码(如果存在不匹配,则更喜欢后者。在 C++ 中,我推荐使用 RTTI 和dynamic_cast
。在 Python 中,isinstance()
。If there is a hard boundary between types of cars and types of fuel, then
FillTank()
has no business being in the baseCar
class, since knowing that you have a car doesn't tell you what kind of fuel. So, for this to ensure correctness at compile time,FillTank()
should be defined in the subclasses, and should only take theFuel
subclass that works.But what if you have common code that you don't want to repeat between the subclasses? Then you write a protected
FillingTank()
method for the base class that the subclass's function calls. Same thing goes forFuel
.But what if you have some magic car that runs on multiple fuels, say diesel or gas? Then that car becomes a subclass of both
DieselCar
andGasCar
and you need to make sure thatCar
is declared as a virtual superclass so you don't have twoCar
instances in aDualFuelCar
object. Filling the tank should Just Work with little or no modification: by default, you'll get bothDualFuelCar.FillTank(GasFuel)
andDualFuelCar.FillTank(DieselFuel)
, giving you an overloaded-by-type function.But what if you don't want the subclass to have a
FillTank()
function? Then you need to switch to run time checking and do what you thought you had to: make the subclass checkFuel.type
and either throw an exception or return an error code (prefer the latter) if there is a mismatch. In C++, RTTI anddynamic_cast<>
are what I would recommend. In Python,isinstance()
.为此可以使用双重调度:在加油之前接受一些燃料。请注意,在不直接支持它的语言中,您引入了依赖项
a double dispatch can be used for this: accept some fuel before before filling. Mind you that in language that don't support it directly, you introduce dependencies
听起来您只是想限制柴油车使用的燃料类型。类似于:
可以解决这个问题,例如
本质上您在这里所做的就是允许
汽车
具有特定的燃料类型。它还允许您创建一辆支持任何类型燃料
的汽车(有机会真是太好了!)。但是,在您的例子中,即 DieselCar,您只需从 car 派生一个类并将其限制为仅使用Diesel
燃料。It sounds like you just want to restrict the type of fuel that goes into your diesel car. Something like:
Would do the trick e.g.
Essentially what you are doing here is allowing a
Car
to have specific fuel types. It also allows you to create a car that would support any type ofFuel
(the chance would be a fine thing!). However, in your case, the DieselCar, you would just derive a class from car and restrict it to usingDiesel
fuel only.使用
is
运算符检查接受的类,并且可以在构造函数中抛出异常use the
is
operator to check against the accepted classes, and you can throw an exception in the constructor我认为可接受的方法是在基类中有一个 ValidFuel(Fuel f) 方法,如果“ leaf”汽车不会覆盖它。
然后,
FillTank
可以完全位于基类中,并调用ValidFuel
来查看它是否有效。I think the accepted method would be to have a
ValidFuel(Fuel f)
method in your base class that throws some sort ofNotImplementedException
(different languages have different terms) if the "leaf" cars don't override it.FillTank
could be then be entirely in the base class and callValidFuel
to see if it's valid.在类似 CLOS 的系统中,你可以做这样的事情:
给你这样的行为:
这实际上是 Common Lisp 的双重调度版本,正如 stefaanv。
In a CLOS-like system, you could do something like this:
giving you this behaviour:
Which, really, is Common Lisp's version of double dispatch, as mentioned by stefaanv.
你可以扩展你原来的汽车接口
}
you can extend your original Car interface
}