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

C#:返回其具体类型在运行时确定的对象的方法?,c#,interface,concrete,C#,Interface,Concrete,我正在考虑设计一个方法,该方法将返回一个实现接口的对象,但其具体类型在运行时之前是未知的。例如,假设: ICar Ford implements ICar Bmw implements ICar Toyota implements ICar public ICar GetCarByPerson(int personId) 直到运行时,我们才知道我们会得到什么车 a) 我想知道这个人有什么类型的车 b) 根据我们得到的具体汽车类型,我们将调用不同的方法(因为有些方法只对类有意义)。因此,客户端

我正在考虑设计一个方法,该方法将返回一个实现接口的对象,但其具体类型在运行时之前是未知的。例如,假设:

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();
}
这个设计好吗?有密码气味吗?有更好的方法吗


我对返回接口的方法很满意,如果客户机代码调用接口方法,显然这也很好。但我关心的是,将客户机代码转换为具体类型是否是良好的设计。

您只需要一个虚拟方法,
SpecificationMethod
,它在每个类中实现。我建议阅读有关继承的内容。他提到的设计方法也可以应用于.Net。

更好的解决方案是让ICar声明一个GenericCarMethod(),并让Bmw和丰田覆盖它。一般来说,如果可以避免的话,依赖向下投射不是一种好的设计实践。

在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时,另一辆车会做一些特别的事情。你会把这项功能添加到驾驶员中,你会考虑到其他需要处理的特殊情况,你会浪费二十分钟的时间去追踪每一个有车的地方,如果(车是Foo),因为它现在分散在整个代码库中-
驾驶员中,车库中,停车场内部
停车场。。。(我是根据在这里处理遗留代码的经验说的。)

当您发现自己在做一个类似于
if(instance is SomeObject)
的语句时,停下来问问自己为什么需要在这里处理这种特殊行为。大多数情况下,它可能是接口/抽象类中的一个新方法,您可以简单地为非“特殊”类提供默认实现

这并不是说你绝对不应该用
is
检查类型;然而,在这种情况下,你必须非常小心,因为除非加以控制,否则它有失控和被滥用的倾向


现在,假设您已经确定您必须最终检查您的
ICar
。使用
的问题在于
静态代码分析工具会在您执行强制转换时警告您两次

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();
}
您可以在枚举上快速地
切换
,它可以让您知道(在某种程度上)可能使用的汽车的具体限制


使用枚举的一个缺点是
CarType
是一成不变的;如果另一个(外部)总成依赖于
ICar
,并且他们添加了新的
Tesla
汽车,他们将无法将
Tesla
类型添加到
CarType
。枚举也不适合于类层次结构:如果希望
Chevy
成为
CarType.Chevy
CarType.GM
,则必须使用枚举作为标志(在本例中很难看),或者确保在
GM
之前检查
Chevy
,或者在针对枚举的检查中有大量的
|

这是一个典型的双重分派问题,它有一个可接受的解决模式(访问者模式)


每个ICar都有这个方法吗…它不能是ICar接口上存在的DoSomething()有什么原因吗?这是一种代码味道。尽管如此,您并不是第一个使用这种方法的人,因为在某些情况下它是有意义的。如果你想得到更好的答案,你可能需要提供更多的细节。。。这些决定中有很多都涉及到一个简单的虚构汽车示例无法揭示的权衡。我不认为你错了,但我认为这有点自以为是。显然,OP有一个问题,“某些方法只在[某些类]上有意义”+1:访问者模式的良好、干净的演示。我希望他们在我必须维护的代码中使用这种代码。哈,我希望我使用我编写的这种我必须维护的代码:DGreat example!最好附上一个实例来实例化正确的ICarVisitor。
//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);
  }
}