C#:返回在运行时确定具体类型的对象的方法?

Use*_*ser 5 c# interface concrete

我正在考虑设计一个方法,该方法将返回一个实现接口的对象,但在运行时才会知道其具体类型.比如假设:

ICar
Ford implements ICar
Bmw implements ICar
Toyota implements ICar

public ICar GetCarByPerson(int personId)
Run Code Online (Sandbox Code Playgroud)

我们不知道在运行之前我们会得到什么车.

a)我想知道这个人有什么类型的汽车.

b)根据我们得到的具体车型,我们会调用不同的方法(因为有些方法只对类有意义).所以客户端代码会做类似的事情.

ICar car = GetCarByPerson(personId);

if ( car is Bmw )
{
  ((Bmw)car).BmwSpecificMethod();
}
else if (car is Toyota)
{
  ((Toyota)car).ToyotaSpecificMethod();
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的设计吗?有代码味吗?有一个更好的方法吗?

我很好用返回接口的方法,如果客户端代码显然调用接口方法,那就没问题了.但我担心的是客户端代码转换为具体类型是否是好的设计.

Mar*_*off 11

is在C#中使用关键字(以上面演示的方式)几乎总是代码味道.它很臭.

问题是,ICar现在需要一些应该只知道a的东西来跟踪实现的几个不同的类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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

后来,另一辆车在你做的时候会做些特别的事情FloorIt.并且你将添加该功能Driver,并且你将考虑需要处理的其他特殊情况,并且你将浪费二十分钟来跟踪每个存在的地方if (car is Foo),因为它现在分散在整个代码库中- 内部Driver,内部Garage,内部ParkingLot...... (我在这里谈论遗留代码的经验.)

当你发现自己发表声明时if (instance is SomeObject),请停下来问自己为什么需要在这里处理这种特殊行为.大多数情况下,它可以是接口/抽象类中的新方法,您可以简单地为非"特殊"的类提供默认实现.

这并不是说你绝对不应该检查类型is; 但是,在这种做法中你必须非常小心,因为它有失控的倾向,除非得到控制,否则会被滥用.


现在,假设您确定最终必须对您进行打字检查ICar.使用的问题is是,当你这样做时,静态代码分析工具会警告你铸造两次

if (car is Bmw)
{
   ((Bmw)car).ShiftLanesWithoutATurnSignal();
}
Run Code Online (Sandbox Code Playgroud)

除非它处于内循环中,否则性能损失可能是微不足道的,但是编写它的首选方法是

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

这只需要一个演员(内部)而不是两个.

如果你不想走那条路,另一个干净的方法是简单地使用枚举:

enum CarType
{
    Bmw,
    Toyota,
    Kia
}

interface ICar
{
    void Go();

    CarType Make
    {
        get;
    }
}
Run Code Online (Sandbox Code Playgroud)

其次是

if (car.Make == CarType.Kia)
{
   ((Kia)car).TalkOnCellPhoneAndGoFifteenUnderSpeedLimit();
}
Run Code Online (Sandbox Code Playgroud)

您可以快速switch使用枚举,它可以让您(在某种程度上)了解可能使用的汽车的具体限制.

使用枚举的一个缺点是一成不变CarType; 如果另一个(外部)组件依赖ICar并且他们添加了Tesla新车,他们将无法添加Tesla类型CarType.枚举也不借给自己很好的类层次结构:如果你想Chevy成为一个CarType.Chevy 一个CarType.GM,你要么必须使用枚举的标志(在这种情况下丑陋的),或确保您检查Chevy之前GM,还是有很多||小号在您对枚举的检查中.


Igo*_*aka 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 哈,我希望我使用**我写的那种我必须维护的代码:D (2认同)