面向对象设计问题

发布于 2024-08-22 05:18:39 字数 362 浏览 5 评论 0原文

在这个简单的例子中,什么是好的设计:

假设我有一个基类 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

酒废 2024-08-29 05:18:39

使用通用基类(如果您的语言支持它(下面是 C#)):

public abstract class Car<TFuel>  where TFuel : Fuel
{
    public abstract void FillTank(TFuel fuel);
}

基本上,这会强制从 car 继承的任何类指定它使用的燃料类型。此外,Car 类强加了一个限制,即 TFuel 必须是抽象 Fuel 类的某种子类型。

假设我们有一些简单的 Diesel 类:

public class Diesel : Fuel
{
    ...
}

以及一辆仅使用柴油运行的汽车:

public DieselCar : Car<Diesel>
{
     public override void FillTank(Diesel fuel)
     {
          //perform diesel fuel logic here.
     }
}

Use a generic base class (if your language supports it (the below is C#)):

public abstract class Car<TFuel>  where TFuel : Fuel
{
    public abstract void FillTank(TFuel fuel);
}

Basically this enforces any class that inherits from car to specify which type of fuel it uses. Furthermore, the Car class imposes a restriction that TFuel must be some subtype of the abstract Fuel class.

Lets say we have some class Diesel which is simple:

public class Diesel : Fuel
{
    ...
}

And a car which only runs on diesel:

public DieselCar : Car<Diesel>
{
     public override void FillTank(Diesel fuel)
     {
          //perform diesel fuel logic here.
     }
}
痞味浪人 2024-08-29 05:18:39

仅靠面向对象编程并不能很好地解决这个问题。您需要的是通用编程(此处显示的 C++ 解决方案):

template <class FuelType>
class Car
{
public:
  void FillTank(FuelType fuel);
};

您的柴油车只是一辆特定的汽车,Car

Object-oriented programming alone cannot handle this problem well. What you need is generic programming (C++ solution shown here):

template <class FuelType>
class Car
{
public:
  void FillTank(FuelType fuel);
};

Your diesel car is then just a specific car, Car<Diesel>.

你げ笑在眉眼 2024-08-29 05:18:39

如果汽车类型和燃料类型之间存在硬性界限,那么 FillTank() 就没有必要存在于 Car 基类中,因为知道您拥有一辆车没有告诉你哪种燃料。因此,为了确保编译时的正确性,应在子类中定义 FillTank(),并且应仅采用该子类的 Fuel 子类作品。

但是,如果您不想在子类之间重复使用公共代码怎么办?然后,为子类的函数调用的基类编写一个protected FillingTank() 方法。 Fuel 也是如此。

但是,如果您有一辆神奇的汽车,可以使用多种燃料(例如柴油或汽油)运行,该怎么办?然后,该汽车将成为 DieselCarGasCar 的子类,并且您需要确保将 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 base Car 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 the Fuel 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 for Fuel.

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 and GasCar and you need to make sure that Car is declared as a virtual superclass so you don't have two Car instances in a DualFuelCar object. Filling the tank should Just Work with little or no modification: by default, you'll get both DualFuelCar.FillTank(GasFuel) and DualFuelCar.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 check Fuel.type and either throw an exception or return an error code (prefer the latter) if there is a mismatch. In C++, RTTI and dynamic_cast<> are what I would recommend. In Python, isinstance().

摘星┃星的人 2024-08-29 05:18:39

为此可以使用双重调度:在加油之前接受一些燃料。请注意,在不直接支持它的语言中,您引入了依赖项

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

深巷少女 2024-08-29 05:18:39

听起来您只是想限制柴油车使用的燃料类型。类似于:

public class Fuel
{
    public Fuel()
    {
    }
}

public class Diesel: Fuel
{
}

public class Car<T> where T: Fuel
{
    public Car()
    {
    }

    public void FillTank(T fuel)
    {
    }
}

public class DieselCar: Car<Diesel>
{
}

可以解决这个问题,例如

var car = new DieselCar();
car.FillTank(/* would expect Diesel fuel only */);

本质上您在这里所做的就是允许汽车具有特定的燃料类型。它还允许您创建一辆支持任何类型燃料的汽车(有机会真是太好了!)。但是,在您的例子中,即 DieselCar,您只需从 car 派生一个类并将其限制为仅使用 Diesel 燃料。

It sounds like you just want to restrict the type of fuel that goes into your diesel car. Something like:

public class Fuel
{
    public Fuel()
    {
    }
}

public class Diesel: Fuel
{
}

public class Car<T> where T: Fuel
{
    public Car()
    {
    }

    public void FillTank(T fuel)
    {
    }
}

public class DieselCar: Car<Diesel>
{
}

Would do the trick e.g.

var car = new DieselCar();
car.FillTank(/* would expect Diesel fuel only */);

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 of Fuel (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 using Diesel fuel only.

伏妖词 2024-08-29 05:18:39

使用 is 运算符检查接受的类,并且可以在构造函数中抛出异常

use the is operator to check against the accepted classes, and you can throw an exception in the constructor

禾厶谷欠 2024-08-29 05:18:39

我认为可接受的方法是在基类中有一个 ValidFuel(Fuel f) 方法,如果“ leaf”汽车不会覆盖它。

然后,FillTank 可以完全位于基类中,并调用 ValidFuel 来查看它是否有效。

public class BaseCar {
    public bool ValidFuel(Fuel f) {
        throw new Exception("IMPLEMENT THIS FUNCTION!!!");
    }

    public void FillTank(Fuel fuel) {
        if (!this.ValidFuel(fuel))
             throw new Exception("Fuel type is not valid for this car.");
        // do what you'd do to fill the car
    }
}

public class DieselCar:BaseCar {
    public bool ValidFuel(Fuel f) {
        return f is DeiselFuel
    }
}

I think the accepted method would be to have a ValidFuel(Fuel f) method in your base class that throws some sort of NotImplementedException (different languages have different terms) if the "leaf" cars don't override it.

FillTank could be then be entirely in the base class and call ValidFuel to see if it's valid.

public class BaseCar {
    public bool ValidFuel(Fuel f) {
        throw new Exception("IMPLEMENT THIS FUNCTION!!!");
    }

    public void FillTank(Fuel fuel) {
        if (!this.ValidFuel(fuel))
             throw new Exception("Fuel type is not valid for this car.");
        // do what you'd do to fill the car
    }
}

public class DieselCar:BaseCar {
    public bool ValidFuel(Fuel f) {
        return f is DeiselFuel
    }
}
早茶月光 2024-08-29 05:18:39

在类似 CLOS 的系统中,你可以做这样的事情:

(defclass vehicle () ())
(defclass fuel () ())
(defgeneric fill-tank (vehicle fuel))
(defmethod fill-tank ((v vehicle) (f fuel)) (format nil "Dude, you can't put that kind of fuel in this car"))

(defclass diesel-truck (vehicle) ())
(defclass normal-truck (vehicle) ())
(defclass diesel (fuel) ())
(defmethod fill-tank ((v diesel-truck) (f diesel)) (format nil "Glug glug"))

给你这样的行为:

CL> (fill-tank (make-instance 'normal-truck) (make-instance 'diesel))
"Dude, you can't put that kind of fuel in this car"
CL> (fill-tank (make-instance 'diesel-truck) (make-instance 'diesel))
"Glug glug"

这实际上是 Common Lisp 的双重调度版本,正如 stefaanv

In a CLOS-like system, you could do something like this:

(defclass vehicle () ())
(defclass fuel () ())
(defgeneric fill-tank (vehicle fuel))
(defmethod fill-tank ((v vehicle) (f fuel)) (format nil "Dude, you can't put that kind of fuel in this car"))

(defclass diesel-truck (vehicle) ())
(defclass normal-truck (vehicle) ())
(defclass diesel (fuel) ())
(defmethod fill-tank ((v diesel-truck) (f diesel)) (format nil "Glug glug"))

giving you this behaviour:

CL> (fill-tank (make-instance 'normal-truck) (make-instance 'diesel))
"Dude, you can't put that kind of fuel in this car"
CL> (fill-tank (make-instance 'diesel-truck) (make-instance 'diesel))
"Glug glug"

Which, really, is Common Lisp's version of double dispatch, as mentioned by stefaanv.

心碎的声音 2024-08-29 05:18:39

你可以扩展你原来的汽车接口

interface Car {
    drive();
}

interface DieselCar extends Car {
    fillTank(Diesel fuel);
}

interface SolarCar extends Car {
    chargeBattery(Sun fuel);
}

}

you can extend your original Car interface

interface Car {
    drive();
}

interface DieselCar extends Car {
    fillTank(Diesel fuel);
}

interface SolarCar extends Car {
    chargeBattery(Sun fuel);
}

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文