如何在双层次结构中正确表示继承?

时间:2013-07-05 20:28:33

标签: c# oop

假设我们有两个继承层次结构,其中包含:

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

理想情况下,所有类型的汽车应该只有一个名为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或更多级别的继承链。我错过了一些显而易见的事情或者是一些技巧吗?

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

这样做是出于危险的理由。首先,您要在特定的汽车类型和它的引擎之间创建紧密耦合。那时不需要继承,或者引擎至少不必存在于基类中。

有一个名为“Law of Demeter”的主体,它表示您不应该公开子对象,而是控制根对象中的行为。例如,使用Car.Start()代替Car.Engine.Start()。否则,根对象将失去对其自身行为的控制。

因此,如果你开始尊重德米特定律原则,你就不会有必须暴露正确的发动机类型的问题,因为这取决于每种车型。

imho你的等级是builder pattern的完美候选人。也就是说,你有一个特殊的类,其唯一的目的是构建你的复杂对象(汽车)。在维基百科中阅读更多相关信息。那篇文章甚至有一个Car builder;)

答案 2 :(得分:0)

你也可以考虑:

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

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

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

因此,您始终在基类中引用相同的引擎,但其实际类型由具体车型标识。

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

  • 小时电力
  • 消费
  • 等..

使用TurboEngineFormulaOnEngine中的适当值覆盖。