3

假设我们有两个相互关联的继承层次结构:

Car -> SportsCar -> SuperCar

Engine -> TurboEngine -> FormulaOneEngine

汽车有发动机。SportsCar 有 TurboEngine,SuperCar 有 FormulaOneEngine。问题是如何在 C# 中正确地表示这一点。假设我们这样做:

class Car {
  Engine Engine;
  int HeadLamps;
}
class SportsCar: Car {
  TurboEngine TurboEngine;
  int Ailerons;
}
class SuperCar: SportsCar {
  FormulaOneEngine FormulaOneEngine;
  int FlyingSaucers;
}

这解决了问题,但它看起来,嗯,丑陋!例如,SuperCar 现在有三个成员,它们通常会指向同一个实例(如果他们不这样做会更糟!)而且它看起来也没有必要。如果有 4、5 或更多级别的深度层次结构,这看起来会非常难看:

SuperCar.Engine
SuperCar.SportsEngine
SuperCar.FormulaOneEngine

理想情况下,所有类型的 Car 都应该只有一个成员,称为 Engine,但它应该是不同的类型,具体取决于汽车:

Car.Engine
SportsCar.Engine //of type TurboEngine
SuperCar.Engine //of type FormulaOneEngine

另外,这应该支持多态性,所以我可以

List<Car> x = ...
x.Add(myCar);
x.Add(mySportsCar);
x.Add(mySuperCar);
foreach(Car c in x)
{
  x.Engine ...
}

所以使用“新”成员已经过时了。我还希望能够为每种引擎类型直接调用特定的方法:

class Engine { int Capacity; }
class TurboEngine: Engine { int TurboCharger; }
class FormulaOneEngine: TurboEngine { int JetFuel; }

myCar.Engine.Capacity = ....
mySportsCar.Engine.TurboCharger = ...
mySuperCar.Engine.JetFuel = ...

我也将能够使用正常的继承好东西:

myCar.HeadLamps = ...
mySportsCar.HeadLamps = ....
mySportsCar.Ailerons = ....
mySuperCar.HeadLamps = ...
mySuperCar.Ailerons = ....
mySuperCar.FlyingSaucers = ....

有什么想法可以重构它以达到目的并且看起来很漂亮吗?实际代码可能使用 4、5 甚至更多级别的继承链。我错过了一些明显的东西还是一些技巧?

4

3 回答 3

6

这是一个经典的通用案例:

再次编辑- 仍然是相同的概念,但继承链完全存在。您必须具有隐藏属性 ( new) 才能执行您需要的操作,但是由于您正在实现ICar接口,因此当您拥有混合集合时,您仍然可以获得基本属性。我认为这是你能做到的最好的。

public interface ICar
{
    IEngine Engine { get; set; }
}

public abstract class BaseCar<T>
    : ICar
    where T : IEngine
{
    public T Engine { get; set; }
    IEngine ICar.Engine { get { return Engine; } set { Engine = (T)value; } }
}

public class Car : BaseCar<Engine>, ICar { }

public class SportsCar : Car, ICar
{
    IEngine ICar.Engine
    {
        get { return Engine; }
        set { Engine = (TurboEngine)value; }
    }

    public new TurboEngine Engine
    {
        get { return (TurboEngine)base.Engine; }
        set { base.Engine = value; }
    }
}
public interface ISportsCar<T> : ICar where T : TurboEngine { }

public class SuperCar : SportsCar, ISportsCar<FormulaOneEngine>
{
    public new FormulaOneEngine Engine
    {
        get { return (FormulaOneEngine)base.Engine; }
        set { base.Engine = value; }
    }
}

public interface IEngine { }
public class Engine : IEngine { }
public class TurboEngine : Engine { }
public class FormulaOneEngine : TurboEngine { }

您现在可以执行以下操作:

var cars = new List<ICar>();
cars.Add(new Car());
cars.Add(new SuperCar());
cars.Add(new SportsCar());

请记住,该Engine属性将IEngine在此上下文中属于接口类型,这意味着您将无法访问每个类的唯一属性,并且如果您尝试将错误的对象分配给该类,则可能会遇到运行时异常财产。但是,如果你说:

var mySportsCar = new SportsCar();
mySportsCar.Engine // this will be strongly typed as TurboEngine
于 2013-07-05T20:32:18.277 回答
1

这样做会让你处于危险的境地。首先,您正在创建特定汽车类型与其引擎之间的紧密耦合。那么继承并不是真正需要的,或者引擎至少不必存在于基类中。

有一个叫做“得墨忒耳法则”的原则,它说你不应该暴露子对象,而是控制根对象的行为。例如使用Car.Start()而不是Car.Engine.Start(). 否则,根对象将失去对其自身行为的控制。

因此,如果您开始尊重得墨忒耳定律原则,您就不必暴露正确的发动机类型,因为这取决于每种汽车类型。

恕我直言,您的层次结构是构建器模式的完美候选者。也就是说,您有一个特殊的类,其唯一目的是构造您的复杂对象(汽车)。在维基百科中阅读更多关于它的信息。那篇文章甚至有一个汽车制造商;)

于 2013-07-05T21:37:43.160 回答
0

您还可以考虑:

class Car {
  Engine Engine;
}
class SportsCar: Car {

  SportsCar() {
     Engine = new TurboEngine();
  }

}
class SuperCar: SportsCar {
   SuperCar() {
     Engine = new FormulaOnEngine();
  }
}

所以你总是在基类中引用同一个引擎,但它的真实类型由具体的汽车类型标识。

Engine将具有virtual描述引擎属性的成员,例如:

  • 小时功率
  • 消耗
  • ETC..

TurboEngine它被和中的适当值覆盖FormulaOnEngine

于 2013-07-05T20:35:33.920 回答