代码之家  ›  专栏  ›  技术社区  ›  User

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

  •  5
  • User  · 技术社区  · 16 年前

    ICar
    Ford implements ICar
    Bmw implements ICar
    Toyota implements ICar
    
    public ICar GetCarByPerson(int personId)
    

    我们不知道什么车我们会回来,直到运行时间。

    b) 根据我们得到的具体汽车类型,我们将调用不同的方法(因为有些方法只对类有意义)。因此客户端代码将执行以下操作。

    ICar car = GetCarByPerson(personId);
    
    if ( car is Bmw )
    {
      ((Bmw)car).BmwSpecificMethod();
    }
    else if (car is Toyota)
    {
      ((Toyota)car).ToyotaSpecificMethod();
    }
    

    这个设计好吗?有密码气味吗?有没有更好的办法?

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

    4 回复  |  直到 16 年前
        1
  •  11
  •   Mark Rushakoff    16 年前

    使用 is C#中的关键字(以上面演示的方式)几乎总是一种代码味道。而且很臭。

    问题是,一些本该只知道 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 Driver ,你会想到其他需要处理的特殊案件,你会浪费20分钟去追踪每一个有犯罪记录的地方 if (car is Foo) ,因为它现在分散在整个代码库中--内部 ,内部 Garage ,内部 ParkingLot (我是根据在这里处理遗留代码的经验说的。)

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

    这并不是说你绝对不应该检查类型 ; 然而,你必须非常小心地在这个实践中,因为它有一个趋势失控,并成为滥用,除非保持在控制。


    现在,假设您已经确定您必须检查您的 . 使用的问题

    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 在石头上;如果另一个(外部)组件依赖于 ICar公司 他们添加了新的 Tesla 车,他们不能再加一辆 键入到 卡式 . 枚举也不适合类层次结构:如果您想要 Chevy 成为一个 CarType.Chevy CarType.GM 雪佛兰 之前 GM ,或者有很多 ||

        2
  •  9
  •   Igor Zevaka    16 年前

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

    //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);
      }
    }
    
        3
  •  0
  •   Anthony Atmaram    16 年前

    你只需要一个虚拟方法, SpecificationMethod ,在每个类中实现。我建议你读书 FAQ Lite 的内容。他提到的设计方法也可以应用于.Net。

        4
  •  0
  •   jwismar    16 年前

    更好的解决方案是让ICar声明GenericCarMethod()并让Bmw和Toyota重写它。一般来说,如果可以避免的话,依赖向下转换不是一个好的设计实践。

    推荐文章