[英]C#: Method to return object whose concrete type is determined at runtime?
我正在考慮設計一個方法,該方法將返回一個實現接口的對象,但在運行時才會知道其具體類型。 比如假設:
ICar
Ford implements ICar
Bmw implements ICar
Toyota implements ICar
public ICar GetCarByPerson(int personId)
我們不知道在運行之前我們會得到什么車。
a)我想知道這個人有什么類型的汽車。
b)根據我們得到的具體車型,我們會調用不同的方法(因為有些方法只對類有意義)。 所以客戶端代碼會做類似的事情。
ICar car = GetCarByPerson(personId);
if ( car is Bmw )
{
((Bmw)car).BmwSpecificMethod();
}
else if (car is Toyota)
{
((Toyota)car).ToyotaSpecificMethod();
}
這是一個很好的設計嗎? 有代碼味嗎? 有一個更好的方法嗎?
我很好用返回接口的方法,如果客戶端代碼顯然調用接口方法,那就沒問題了。 但我擔心的是客戶端代碼轉換為具體類型是否是好的設計。
在C#中使用is
關鍵字(以上面演示的方式)幾乎總是代碼味道。 它很臭。
問題是,現在需要一些只能知道ICar
東西來跟蹤實現ICar
的幾個不同的類。 雖然這有效(因為它產生的代碼可以運行),但它的設計很糟糕。 你將開始只有幾輛車......
class Driver
{
private ICar car = GetCarFromGarage();
public void FloorIt()
{
if (this.car is Bmw)
{
((Bmw)this.car).AccelerateReallyFast();
}
else if (this.car is Toyota)
{
((Toyota)this.car).StickAccelerator();
}
else
{
this.car.Go();
}
}
}
后來,當你在FloorIt
時, 另一輛車會做一些特別的FloorIt
。 並且你會將這個功能添加到Driver
,你會考慮需要處理的其他特殊情況,並且你會浪費二十分鍾來追蹤每個有if (car is Foo)
,因為它是現在分散在整個代碼庫中 - 在Driver
內部,在Garage
內部,在ParkingLot
... (我在這里談論遺留代碼的經驗。)
當你發現自己發出if (instance is SomeObject)
類的語句時,請停下來問問自己為什么需要在這里處理這種特殊行為。 大多數情況下,它可以是接口/抽象類中的新方法,您可以簡單地為非“特殊”的類提供默認實現。
這並不是說你絕對不應該用is
檢查類型; 但是,在這種做法中你必須非常小心,因為它有失控的傾向,除非得到控制,否則會被濫用。
現在,假設您已確定最終必須對您的ICar
檢查。 使用的問題is
,當你這樣做時,靜態代碼分析工具會警告你鑄造兩次
if (car is Bmw)
{
((Bmw)car).ShiftLanesWithoutATurnSignal();
}
除非它處於內循環中,否則性能損失可能是微不足道的,但是編寫它的首選方法是
var bmw = car as Bmw;
if (bmw != null) // careful about overloaded == here
{
bmw.ParkInThreeSpotsAtOnce();
}
這只需要一個演員(內部)而不是兩個。
如果你不想走那條路,另一個干凈的方法是簡單地使用枚舉:
enum CarType
{
Bmw,
Toyota,
Kia
}
interface ICar
{
void Go();
CarType Make
{
get;
}
}
其次是
if (car.Make == CarType.Kia)
{
((Kia)car).TalkOnCellPhoneAndGoFifteenUnderSpeedLimit();
}
您可以快速switch
枚舉,它可以讓您(在某種程度上)了解可能使用的汽車的具體限制。
使用枚舉的一個缺點是CarType
是CarType
; 如果另一個(外部)組件依賴於ICar
並且他們添加了新的Tesla
汽車,他們將無法將Tesla
類型添加到CarType
。 枚舉也不適合類層次結構:如果你想讓Chevy
成為CarType.Chevy
和 CarType.GM
,你必須使用枚舉作為標志(在這種情況下是丑陋的)或確保你檢查在GM
之前Chevy
,或者有很多||
在你的支票核對。
這是一個經典的雙重調度問題,它有一個可接受的解決方案(訪客模式)。
//This is the car operations interface. It knows about all the different kinds of cars it supports
//and is statically typed to accept only certain ICar subclasses as parameters
public interface ICarVisitor {
void StickAccelerator(Toyota car); //credit Mark Rushakoff
void ChargeCreditCardEveryTimeCigaretteLighterIsUsed(Bmw car);
}
//Car interface, a car specific operation is invoked by calling PerformOperation
public interface ICar {
public string Make {get;set;}
public void PerformOperation(ICarVisitor visitor);
}
public class Toyota : ICar {
public string Make {get;set;}
public void PerformOperation(ICarVisitor visitor) {
visitor.StickAccelerator(this);
}
}
public class Bmw : ICar{
public string Make {get;set;}
public void PerformOperation(ICarVisitor visitor) {
visitor.ChargeCreditCardEveryTimeCigaretteLighterIsUsed(this);
}
}
public static class Program {
public static void Main() {
ICar car = carDealer.GetCarByPlateNumber("4SHIZL");
ICarVisitor visitor = new CarVisitor();
car.PerformOperation(visitor);
}
}
您只需要一個虛擬方法, SpecificationMethod
,它在每個類中實現。 我建議閱讀FAQ Lite關於遺產的內容。 他提到的設計方法也可以應用於.Net。
一個更好的解決方案是讓ICar聲明一個GenericCarMethod()並讓Bmw和Toyota覆蓋它。 一般來說,如果你可以避免它,那么依靠向下轉換並不是一個好的設計實踐。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.