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();
}
这是一个好的设计吗?有代码味吗?有更好的方法吗?
我对返回接口的方法很满意,如果客户端代码调用接口方法,显然这就没问题。但我担心的是客户端代码转换为具体类型是否是好的设计。
I'm thinking about designing a method that would return an object that implements an interface but whose concrete type won't be know until run-time. For example suppose:
ICar
Ford implements ICar
Bmw implements ICar
Toyota implements ICar
public ICar GetCarByPerson(int personId)
We don't know what car we will get back until runtime.
a) I want to know what type of car the person has.
b) depending on the concrete car type we get back we will call different methods (because some methods only make sense on the class). So the client code will do something like.
ICar car = GetCarByPerson(personId);
if ( car is Bmw )
{
((Bmw)car).BmwSpecificMethod();
}
else if (car is Toyota)
{
((Toyota)car).ToyotaSpecificMethod();
}
Is this a good design? Is there a code smell? Is there a better way to do this?
I'm fine with the method that returns the interface, and if the client code was calling interface methods obviously this would be fine. But my concern is whether the client code casting to concrete types is good design.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在 C# 中使用
is
关键字(按照上面演示的方式)几乎总是有代码味道。而且很臭。问题是,现在需要一些本来只知道
ICar
的东西来跟踪实现ICar
的几个不同的类。虽然这有效(因为它生成了可运行的代码),但它的设计很糟糕。一开始你只需要几辆车...然后,当你
FloorIt
时,另一辆车会做一些特别的事情。您将将该功能添加到Driver
中,并且您将考虑需要处理的其他特殊情况,并且您将浪费 20 分钟来跟踪存在的每个位置>if (car is Foo)
,因为它现在分散在整个代码库中 - 在Driver
内、Garage
内、在ParkingLot
内code>... (我是根据处理遗留代码的经验来发言的。)当您发现自己做出像
if (instance is SomeObject)
这样的语句时,请停止并问问自己为什么需要在这里处理这种特殊行为。大多数时候,它可以是接口/抽象类中的新方法,并且您可以简单地为不“特殊”的类提供默认实现。这并不是说您绝对不应该使用
is
检查类型;然而,在这种做法中你必须非常小心,因为如果不加以控制,它很可能会失控并被滥用。现在,假设您已确定必须对您的
ICar
进行类型检查。使用is
的问题是,静态代码分析工具会警告您两次强制转换,当您这样做时,性能影响可能可以忽略不计,除非它位于内部循环中,但编写此代码的首选方式是
这样只需要一次转换(内部)而不是两次。
如果您不想走这条路,另一种干净的方法是简单地使用枚举:
后跟
You can fast
switch
on an enum,它可以让您(在某种程度上)了解具体的情况可以使用的汽车的限制。使用枚举的一个缺点是
CarType
是一成不变的;如果另一个(外部)程序集依赖于ICar
并且他们添加了新的Tesla
汽车,他们将无法添加Tesla
类型汽车类型
。枚举也不太适合类层次结构:如果您希望Chevy
成为CarType.Chevy
和CarType .GM
,您要么必须使用枚举作为标志(在本例中很丑陋),要么确保在GM
之前检查Chevy
,或者有很多||
在您对枚举的检查中。Using the
is
keyword in C# (in the manner you have demonstrated above) is almost always a code smell. And it stinks.The problem is that something which is supposed to only know about an
ICar
is now required to keep track of several different classes that implementICar
. While this works (as in it produces code that operates), it's poor design. You're going to start off with just a couple cars...And later on, another car is going to do something special when you
FloorIt
. And you'll add that feature toDriver
, and you'll think about the other special cases that need to be handled, and you'll waste twenty minutes tracking down every place that there is aif (car is Foo)
, since it's scattered all over the code base now -- insideDriver
, insideGarage
, insideParkingLot
... (I'm speaking from experience in working on legacy code here.)When you find yourself making a statement like
if (instance is SomeObject)
, stop and ask yourself why this special behavior needs to be handled here. Most of the time, it can be a new method in the interface/abstract class, and you can simply provide a default implementation for the classes that aren't "special".That's not to say that you should absolutely never check types with
is
; however, you must be very careful in this practice because it has a tendency to get out of hand and become abused unless kept in check.Now, suppose you have determined that you conclusively must type-check your
ICar
. The problem with usingis
is that static code analysis tools will warn you about casting twice, when you doThe performance hit is probably negligible unless it's in an inner loop, but the preferred way of writing this is
This requires only one cast (internally) instead of two.
If you don't want to go that route, another clean approach is to simply use an enumeration:
followed by
You can quickly
switch
on an enum, and it lets you know (to some extent) the concrete limit of what cars might be used.One downside to using an enum is that
CarType
is set in stone; if another (external) assembly depends onICar
and they added the newTesla
car, they won't be able to add aTesla
type toCarType
. Enums also don't lend themselves well to class hierarchies: if you want aChevy
to be aCarType.Chevy
and aCarType.GM
, you either have to use the enum as flags (ugly in this case) or make sure you check forChevy
beforeGM
, or have lots of||
s in your checks against the enums.这是一个经典的双重调度问题,它有一个可接受的解决模式(访问者模式)。
This is a classic double dispatch problem and it has an acceptable pattern for solving it (Visitor pattern).
您只需要一个虚拟方法
SpecificationMethod
,它在每个类中实现。我建议阅读 FAQ Lite 有关继承的内容。他提到的设计方法也可以应用于.Net。You would want just a virtual method,
SpecificationMethod
, which is implemented in each class. I recommend reading FAQ Lite's content on inheritence. The design method's he mentions can be applied to .Net as well.更好的解决方案是让 ICar 声明 GenericCarMethod() 并让 Bmw 和 Toyota 覆盖它。一般来说,如果可以避免的话,依赖向下转型并不是一个好的设计实践。
A better solution would have ICar declare a GenericCarMethod() and have Bmw and Toyota override it. In general, it's not a good design practice to rely on downcasting if you can avoid it.