5

インターフェイスを実装するオブジェクトを返すメソッドを設計することを考えていますが、その具体的なタイプは実行時までわかりません。たとえば、次のように仮定します。

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();
}

これは良いデザインですか?コードの臭いはありますか?これを行うためのより良い方法はありますか?

インターフェイスを返すメソッドは問題ありません。クライアントコードがインターフェイスメソッドを呼び出している場合は、明らかにこれで問題ありません。しかし、私の懸念は、具象型へのクライアントコードのキャストが優れた設計であるかどうかです。

4

4 に答える 4

11

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。そして、その機能をに追加しDriver、処理する必要のある他の特殊なケースについて考えます。if (car is Foo)コードベース全体に散在しているため、が存在するすべての場所を追跡するのに20分も無駄になります。 --inside Driver、inside Garage、inside ParkingLot... (私はここでレガシーコードに取り組んだ経験から話しています。)

のような発言をしていることに気付いたらif (instance is SomeObject)、立ち止まって、なぜこの特別な行動をここで処理する必要があるのか​​を自問してください。ほとんどの場合、これはinterface / abstractクラスの新しいメソッドである可能性があり、「特別」ではないクラスのデフォルトの実装を提供するだけです。

だからといって、絶対にタイプをチェックしてはいけないというわけではありませんis。ただし、この方法では、チェックを怠ると手に負えなくなり、虐待される傾向があるため、十分に注意する必要があります。


ここで、最終的にタイプチェックする必要があると判断したとしますICar。使用の問題isは、静的コード分析ツールを使用すると、キャストについて2回警告することです。

if (car is Bmw)
{
   ((Bmw)car).ShiftLanesWithoutATurnSignal();
}

内部ループにない限り、パフォーマンスへの影響はおそらく無視できますが、これを記述するための好ましい方法は次のとおりです。

var bmw = car as Bmw;
if (bmw != null) // careful about overloaded == here
{
    bmw.ParkInThreeSpotsAtOnce();
}

これには、2つではなく1つのキャスト(内部)のみが必要です。

そのルートに行きたくない場合は、別のクリーンなアプローチは単に列挙型を使用することです。

enum CarType
{
    Bmw,
    Toyota,
    Kia
}

interface ICar
{
    void Go();

    CarType Make
    {
        get;
    }
}

に続く

if (car.Make == CarType.Kia)
{
   ((Kia)car).TalkOnCellPhoneAndGoFifteenUnderSpeedLimit();
}

列挙型をすばやくswitch作成でき、使用される可能性のある車の具体的な制限を(ある程度)知ることができます。

列挙型を使用することの1つの欠点はCarType、石に設定されていることです。別の(外部)アセンブリが依存しICarていて、新しい車を追加した場合、にタイプをTesla追加することはできません。列挙型はクラス階層にも適していません。aaにしたい場合は、列挙型をフラグとして使用するか(この場合は醜い)、前に確認するか、または多くのsを使用する必要があります。列挙型に対するチェックで。TeslaCarTypeChevyCarType.Chevy CarType.GMChevyGM||

于 2010-06-04T02:07:12.130 に答える
9

これは古典的なダブルディスパッチの問題であり、それを解決するための許容可能なパターン(ビジターパターン)があります。

//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);
  }
}
于 2010-06-04T02:23:53.110 に答える
0

SpecificationMethod各クラスに実装されている仮想メソッド、だけが必要になります。継承に関するFAQLiteのコンテンツを読むことをお勧めします。彼が言及している設計方法は、.Netにも適用できます。

于 2010-06-04T02:01:26.617 に答える
0

より良い解決策は、ICarにGenericCarMethod()を宣言させ、BmwとToyotaにそれをオーバーライドさせることです。一般に、ダウンキャストを回避できる場合は、ダウンキャストに依存することは適切な設計手法ではありません。

于 2010-06-04T02:01:45.323 に答える