Как использовать композицию с наследованием?
-
07-07-2019 - |
Вопрос
Я попробую задать свой вопрос в контексте простого примера...
Допустим, у меня есть абстрактный базовый класс Car.Автомобиль имеет базовый объект Engine.У меня есть метод StartEngine() в абстрактном классе Car, который делегирует запуск двигателя объекту Engine.
Как разрешить подклассам Car (например, Ferrari) объявлять объект Engine как двигатель определенного типа (например, TurboEngine)?Нужен ли мне другой класс автомобиля (TurboCar)?
Я наследую простой старый объект Engine и не могу повторно объявить (или переопределить) его как TurboEngine в своих подклассах Car.
РЕДАКТИРОВАТЬ: Я понимаю, что могу подключить любой подкласс Engine к ссылке myEngine внутри моего класса Ferrari... но как я могу вызывать методы, которые предоставляет только TurboEngine?Поскольку myEngine унаследован как базовый движок, никакие турбо-функции не включены.
Спасибо!
Решение
Паттерн «Абстрактная фабрика» предназначен именно для решения этой проблемы.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();
}
}
Другие советы
Нет необходимости указывать подкласс Car для использования TurboEngine, если TurboEngine является подклассом Engine.Вы можете просто указать экземпляр TurboEngine в качестве двигателя для вашего Ferrari.Вы даже можете установить дизельный двигатель на свой Ferrari.Они все просто Двигатели.
У автомобиля есть двигатель.TurboEngine — это двигатель.Автомобиль может иметь турбодвигатель, дизельный двигатель или двигатель Флинстоунов.Они все Двигатели.
Если вы хотите ограничить тип двигателя в подклассе вашего автомобиля (без LawnMowerEngine в спортивном автомобиле), вы можете оставить его объявленным как Engine и ограничить его в методах установки.
Отношение «Автомобиль имеет двигатель» не ограничивает применимые подклассы двигателя.
Вы всегда можете использовать защищенный реферат.Публичный «Старт» вызовет защищенный (который будет переопределен в абстрактном классе).Таким образом, вызывающий абонент видит только 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();
Я думаю, это сработает.
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();
Вы можете использовать дженерики 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!");
}
}
Насколько я понимаю ваш (обновленный) вопрос, вам придется завести двигатель автомобиля в TurboEngine
напишите, если хотите позвонить TurboEngine
методы на нем.Это приводит к необходимости тщательно проверять, есть ли в вашей машине TurboEngine
прежде чем вызывать эти методы, но это то, что вы получаете.Не зная, что на самом деле представляет собой эта машина, я не могу придумать никакой причины, по которой двигатель и турбодвигатель не могли бы использовать один и тот же интерфейс - существуют ли действительно новые методы, которые поддерживает турбонаддув, или это просто так? одно и то же по-разному - но я думаю, что эта метафора рано или поздно развалится.
Есть ли у вас дженерики на вашем языке?В 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).
В зависимости от семантики вашего конкретного языка есть несколько способов сделать это.Моя первоначальная мысль заключалась в том, чтобы предоставить защищенный конструктор:
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());
}
}
не раскрывайте внутреннюю часть вашего класса в интерфейсе - другими словами, общедоступным методом Car должен быть Start, а не StartEngine.
если вы хотите навязать внутреннюю структуру (т.е.как если бы у вас был только один движок), то вам понадобится другой движок абстрактного/базового класса, который можно специализировать.
затем вы можете построить спортивный автомобиль из деталей, установив член m_engine в спортивный подкласс Engine и т. д.
РЕДАКТИРОВАТЬ:обратите внимание, что в реальном мире турбокомпрессор не является частью двигателя, а является надстройкой к двигателю со своим интерфейсом управления...Но если вы хотите включить подобные вещи в свой двигатель Ferrari, это нормально, просто выполните преобразование в подклассе SportsCar, чтобы превратить базовый двигатель в TurboEngine.
но было бы лучше при моделировании хранить компоненты отдельно - таким образом вы можете модернизировать свой турбокомпрессор (например, с двойным впуском вместо одинарного), не заменяя весь двигатель!
Есть много способов это сделать.
Я бы предпочел иметь setEngine()
метод на Car
, то имея Ferrari
вызов конструктора setEngine()
и передать экземпляр TurboEngine
.