C#:返回在运行时确定具体类型的对象的方法?
我正在考虑设计一个方法,该方法将返回一个实现接口的对象,但其具体类型在运行时才会知道。 比如假设:
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
。 你将把这个function添加到Driver
,你会考虑需要处理的其他特殊情况,并且你会浪费20分钟跟踪每个有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覆盖它。 一般来说,如果你可以避免它,那么依靠向下转换并不是一个好的设计实践。