簡體   English   中英

C#:返回在運行時確定具體類型的對象的方法?

[英]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枚舉,它可以讓您(在某種程度上)了解可能使用的汽車的具體限制。

使用枚舉的一個缺點是CarTypeCarType ; 如果另一個(外部)組件依賴於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.

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