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
,还是有很多||
小号在您对枚举的检查中.
这是一个经典的双重调度问题,它有一个可接受的解决方案(访客模式).
//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)
归档时间: |
|
查看次数: |
1073 次 |
最近记录: |