假设我们有两个继承层次结构,其中包含:
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或更多级别的继承链。我错过了一些显而易见的事情或者是一些技巧吗?
答案 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
个成员来描述引擎属性,例如:
使用TurboEngine
和FormulaOnEngine
中的适当值覆盖。