Как использовать композицию с наследованием?
Я попытаюсь задать свой вопрос в контексте простого примера...
Допустим, у меня есть абстрактный базовый класс автомобилей. Автомобиль имеет - базовый объект двигателя. У меня есть метод StartEngine() в абстрактном классе Car, который делегирует запуск двигателя объекту Engine.
Как мне разрешить подклассам Car (например, Ferrari) объявлять объект Engine как определенный тип двигателя (например, TurboEngine)? Нужен ли мне другой класс автомобилей (TurboCar)?
Я наследую простой старый объект Engine и не могу повторно объявить (или переопределить) его как TurboEngine в моих подклассах Car.
РЕДАКТИРОВАТЬ: Я понимаю, что я могу подключить любой подкласс Engine в ссылку myEngine в моем классе Ferrari... но как я могу вызвать методы, которые предоставляет только TurboEngine? Поскольку myEngine унаследован как базовый движок, ни один из турбо-компонентов не включен.
Спасибо!
10 ответов
Шаблон Абстрактной Фабрики как раз для этой проблемы. Google GoF Abstract Factory {ваш предпочитаемый язык}
Далее обратите внимание, как вы можете использовать бетонные фабрики для создания "законченных" объектов (enzo, civic) или использовать их для создания "семейств" связанных объектов (CarbonFrame + TurboEngine, WeakFrame + WeakEngine). В конечном итоге, вы всегда получаете объект Car, который реагирует на ускорение () с определенным типом поведения.
using System;
abstract class CarFactory
{
public static CarFactory FactoryFor(string manufacturer){
switch(manufacturer){
case "Ferrari" : return new FerrariFactory();
case "Honda" : return new HondaFactory();
default:
throw new ArgumentException("Unknown car manufacturer. Please bailout industry.");
}
}
public abstract Car createCar();
public abstract Engine createEngine();
public abstract Frame createFrame();
}
class FerrariFactory : CarFactory
{
public override Car createCar()
{
return new Ferrari(createEngine(), createFrame());
}
public override Engine createEngine()
{
return new TurboEngine();
}
public override Frame createFrame()
{
return new CarbonFrame();
}
}
class HondaFactory : CarFactory
{
public override Car createCar()
{
return new Honda(createEngine(), createFrame());
}
public override Engine createEngine()
{
return new WeakEngine();
}
public override Frame createFrame()
{
return new WeakFrame();
}
}
abstract class Car
{
private Engine engine;
private Frame frame;
public Car(Engine engine, Frame frame)
{
this.engine = engine;
this.frame = frame;
}
public void accelerate()
{
engine.setThrottle(1.0f);
frame.respondToSpeed();
}
}
class Ferrari : Car
{
public Ferrari(Engine engine, Frame frame) : base(engine, frame)
{
Console.WriteLine("Setting sticker price to $250K");
}
}
class Honda : Car
{
public Honda(Engine engine, Frame frame) : base(engine, frame)
{
Console.WriteLine("Setting sticker price to $25K");
}
}
class KitCar : Car
{
public KitCar(String name, Engine engine, Frame frame)
: base(engine, frame)
{
Console.WriteLine("Going out in the garage and building myself a " + name);
}
}
abstract class Engine
{
public void setThrottle(float percent)
{
Console.WriteLine("Stomping on accelerator!");
typeSpecificAcceleration();
}
protected abstract void typeSpecificAcceleration();
}
class TurboEngine : Engine
{
protected override void typeSpecificAcceleration()
{
Console.WriteLine("Activating turbo");
Console.WriteLine("Making noise like Barry White gargling wasps");
}
}
class WeakEngine : Engine
{
protected override void typeSpecificAcceleration()
{
Console.WriteLine("Provoking hamster to run faster");
Console.WriteLine("Whining like a dentist's drill");
}
}
abstract class Frame
{
public abstract void respondToSpeed();
}
class CarbonFrame : Frame
{
public override void respondToSpeed()
{
Console.WriteLine("Activating active suspension and extending spoilers");
}
}
class WeakFrame : Frame
{
public override void respondToSpeed()
{
Console.WriteLine("Loosening bolts and vibrating");
}
}
class TestClass
{
public static void Main()
{
CarFactory ferrariFactory = CarFactory.FactoryFor("Ferrari");
Car enzo = ferrariFactory.createCar();
enzo.accelerate();
Console.WriteLine("---");
CarFactory hondaFactory = CarFactory.FactoryFor("Honda");
Car civic = hondaFactory.createCar();
civic.accelerate();
Console.WriteLine("---");
Frame frame = hondaFactory.createFrame();
Engine engine = ferrariFactory.createEngine();
Car kitCar = new KitCar("Shaker", engine, frame);
kitCar.accelerate();
Console.WriteLine("---");
Car kitCar2 = new KitCar("LooksGreatGoesSlow", hondaFactory.createEngine(), ferrariFactory.createFrame());
kitCar2.accelerate();
}
}
Вы всегда можете использовать реферат, который защищен. Публичный "Start" будет вызывать защищенный (это будет ovveride в абстрактном классе). Таким образом, вызывающая сторона видит только Start(), а не StartEngine ().
abstract class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
protected Car(Engine engine) {
this.engine = engine;
}
public void Start()
{
this.StartEngine();
}
protected abstract void StartEngine();
}
public class Ferrari : Car
{
public Ferrari() {
}
protected override void StartEngine()
{
Console.WriteLine("TURBO ENABLE!!!");
}
}
-Как использовать это:
Car c = new Ferrari();
c.Start();
Нет необходимости указывать подкласс Car для TurboEngine, если TurboEngine является подклассом Engine. Вы можете просто указать экземпляр TurboEngine в качестве двигателя для вашего Ferrari. Вы даже можете поставить дизельный двигатель в свой Ferrari. Они все просто двигатели.
У автомобиля есть двигатель. TurboEngine - это двигатель. Автомобиль может иметь TurboEngine, DieselEngine или FlintstonesEngine. Они все Двигатели.
Если вы хотите ограничить тип Engine в своем подклассе Car (без LawnMowerEngine в SportsCar), вы можете оставить его объявленным как Engine и ограничить его в методах установки.
Автомобиль имеет отношение Двигатель не ограничивает применимые подклассы Двигателя.
Как я понимаю ваш (обновленный) вопрос, вам придется отвести двигатель автомобиля на TurboEngine
введите, если вы хотите позвонить TurboEngine
методы на это. В результате много проверяется, есть ли у вас машина TurboEngine
прежде чем вызывать эти методы, но это то, что вы получаете. Не зная, для чего на самом деле стоит этот автомобиль, я не могу представить себе причину, по которой двигатель и турбодвигатель не могут использовать один и тот же интерфейс - существуют ли действительно новые методы, которые поддерживает турбонагнетатель, или он просто делает это? одни и те же вещи по-разному - но я думаю, что эта метафора рано или поздно развалится.
Я думаю, что это будет работать.
public class Car
{
private Engine engine;
public virtual Engine CarEngine
{
get { return engine;}
}
public StartEngine()
{
CarEngine.Start();
}
}
public class Engine
{
public virtual void Start()
{
Console.Writeline("Vroom");
}
}
public class TurboEngine : Engine
{
public override void Start()
{
Console.Writeline("Vroom pSHHHHHHH");
}
// TurboEngine Only method
public double BoostPressure()
{
}
}
public class Ferrari : Car
{
private TurboEngine engine;
public override Engine CarEngine
{
return engine;
}
}
Ferrari = car new Ferrari();
// Will call Start on TurboEngine()
car.StartEngine();
// Upcast to get TurboEngine stuff
Console.WriteLine(car.CarEngine as TurboEngine).BoostPressure();
В зависимости от вашей конкретной языковой семантики, есть несколько способов сделать это. Вначале я думал о том, чтобы создать защищенный конструктор:
public class Car {
private Engine engine;
public Car() {
this(new Engine());
}
protected Car(Engine engine) {
this.engine = engine;
}
public void start() {
this.engine.start();
}
}
public class Ferrari {
public Ferrari() {
super(new TurboEngine());
}
}
Вы можете использовать дженерики C#, чтобы получить то, что вы ищете, здесь.
Различие в использовании дженериков заключается в том, что ваш Ferrari
"знает", что его Engine
это TurboEngine
, в то время как Car
класс не должен знать ничего нового - только то, что EngineType
является Engine
,
class Program
{
static void Main(string[] args)
{
Ferrari ferarri = new Ferrari();
ferarri.Start();
ferarri.Boost();
}
}
public class Car<EngineType> where EngineType : Engine, new()
{
protected EngineType engine;
public Car()
{
this.CreateEngine();
}
protected void CreateEngine()
{
this.engine = new EngineType();
}
public void Start()
{
engine.Start();
}
}
public class Ferrari : Car<TurboEngine>
{
public void Boost()
{
engine.Boost();
}
}
public class Engine
{
public virtual void Start()
{
Console.WriteLine("Vroom!");
}
}
public class TurboEngine : Engine
{
public void Boost()
{
Console.WriteLine("Hang on to your teeth...");
}
public override void Start()
{
Console.WriteLine("VROOOOM! VROOOOM!");
}
}
У вас есть дженерики на вашем языке? В Java я мог бы сделать это:
class Engine {}
abstract class Car<E extends Engine>
{
private E engine;
public E getEngine() { return engine; }
}
class TurboEngine extends Engine {}
class Ferrari extends Car<TurboEngine>
{
// Ferrari now has a method with this signature:
// public TurboEngine getEngine() {}
}
Я уверен, что есть что-то подобное в C#. Затем вы можете рассматривать экземпляр Ferrari как экземпляр подкласса Ferrari (с getEngine, возвращающим TurboEngine) или как экземпляр суперкласса Car (когда getEngine вернет Engine).
Не выставляйте внутренности вашего класса в интерфейсе - другими словами, публичный метод Car должен быть Start, а не StartEngine
Если вы хотите навязать внутреннюю структуру (например, иметь только 1 движок), вам нужен другой абстрактный / базовый класс Engine, который может быть специализированным.
затем вы можете построить спортивный автомобиль из частей, установив член m_engine в спортивный подкласс Engine и др.
РЕДАКТИРОВАТЬ: обратите внимание, что в реальном мире турбокомпрессор не является частью двигателя, это дополнение к двигателю, со своим собственным интерфейсом управления... Но если вы хотите включить такие вещи в свой двигатель Ferrari, ничего страшного, просто превзойти в подклассе SportsCar, чтобы превратить ваш базовый движок в TurboEngine
но было бы лучше, если бы моделирование разделяло компоненты - таким образом, вы можете модернизировать свой турбокомпрессор (например, двойной впуск или один впуск), не заменяя весь двигатель!
Есть много способов сделать это.
Я бы предпочел иметь setEngine()
метод на Car
затем, имея Ferrari
вызов конструктора setEngine()
и передать в случае TurboEngine
,