簡體   English   中英

OOP設計問題

[英]OOP design problem

在這種簡單情況下,什么是好的設計:

假設我有一個使用FillTank(Fuel fuel)方法的基本類Car,其中燃料也是一個基本類,它具有多個葉子類,柴油,乙醇等。

在我的私家車類DieselCar.FillTank(Fuel fuel)僅允許使用某種類型的燃料(那里沒有任何驚喜:)。 現在這是我關心的問題,根據我的界面,每輛汽車都可以裝滿任何燃料,但這對我來說似乎是錯誤的,在每個FillTank()實現中,請檢查輸入燃料的類型是否正確,如果沒有,則拋出錯誤。

我如何才能將這種情況重新設計為更准確的情況,甚至有可能嗎? 如何設計一個采用基類輸入的基本方法,而又不會得到這些“奇怪的結果”?

使用通用基類(如果您的語言支持(以下為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.
     }
}

僅面向對象的編程不能很好地解決此問題。 您需要的是通用編程(此處顯示C ++解決方案):

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

這樣,您的柴油車就是特定的汽車Car<Diesel>

如果在汽車類型和燃料類型之間存在硬性界限,則FillTank()不在基礎Car類中,因為知道您擁有汽車並不能告訴您哪種燃料。 因此,為了確保在編譯時的正確性,應該在子類中定義FillTank() ,並且應該僅使用有效的Fuel子類。

但是,如果您有不想在子類之間重復的通用代碼怎么辦? 然后,為子類的函數調用的基類編寫一個受保護的 FillingTank()方法。 Fuel

但是,如果您有一輛使用多種燃料(例如柴油或汽油)運行的魔術車怎么辦? 然后,該汽車成為DieselCarGasCar的子類,您需要確保將Car聲明為虛擬超類,以便在DualFuelCar對象中沒有兩個Car實例。 只需很少或不做任何修改就可以填充水箱:默認情況下,您將同時獲得DualFuelCar.FillTank(GasFuel)DualFuelCar.FillTank(DieselFuel) ,從而使您可以按類型重載。

但是,如果您不希望子類具有FillTank()函數,該怎么辦? 然后,您需要切換到運行時檢查,並執行您想做的事情:讓子類檢查Fuel.type並拋出異常,或者如果存在不匹配,則返回錯誤代碼(建議使用后者)。 在C ++中,我建議使用RTTI和dynamic_cast<> 在Python中, isinstance()

可以使用雙重調度 :在加油之前先接受一些燃料。 請注意,使用不直接支持的語言會引入依賴關系

聽起來您只是想限制進入柴油車的燃料類型。 就像是:

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 */);

本質上,您在這里所做的就是允許Car使用特定的燃料類型。 它還允許您創建支持任何類型的Fuel的汽車(機會是一件好事!)。 但是,對於您的情況,DieselCar,您只是從汽車派生一個類,並將其限制為僅使用Diesel燃料。

使用is運算符檢查接受的類,然后可以在構造函數中引發異常

我認為,可以接受的方法是在基類中具有ValidFuel(Fuel f)方法,如果“葉”車不重寫該方法, ValidFuel(Fuel f)拋出某種NotImplementedException (不同的語言具有不同的術語)。

然后, 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
    }
}

在類似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"

正如stefaanv所提到的,這實際上是Common Lisp的double dispatch版本。

您可以擴展原始的Car界面

interface Car {
    drive();
}

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

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

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM