[英]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
。
但是,如果您有一輛使用多種燃料(例如柴油或汽油)運行的魔術車怎么辦? 然后,該汽車成為DieselCar
和GasCar
的子類,您需要確保將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.