Как использовать композицию с наследованием?

Я попытаюсь задать свой вопрос в контексте простого примера ...

Допустим, у меня есть абстрактный базовый класс Car. Автомобиль имеет - базовый объект Engine. У меня есть метод StartEngine () в абстрактном классе Car, который делегирует запуск двигателя объекту Engine.

Как разрешить подклассам Car (например, Ferrari) объявлять объект Engine как двигатель определенного типа (например, TurboEngine)? Нужен ли мне другой класс автомобилей (TurboCar)?

Я наследую простой старый объект Engine и не могу повторно объявить (или переопределить) его как TurboEngine в своих подклассах Car.

Обновлено: Я понимаю, что могу подключить любой подкласс Engine к ссылке myEngine в моем классе Ferrari ... но как я могу вызывать методы, которые предоставляет только TurboEngine? Поскольку myEngine унаследован как базовый движок, ничего турбо не включается.

Спасибо!

Ооооо! Чудесно! Никогда не знал, что в SO есть тег аналогия с автомобилем!

Bruno Reis 18.11.2011 00:22
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Принцип подстановки Лискова
Принцип подстановки Лискова
Принцип подстановки Лискова (LSP) - это принцип объектно-ориентированного программирования, который гласит, что объекты суперкласса должны иметь...
7
1
1 059
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Есть много способов сделать это.

Я бы предпочел иметь метод setEngine() на Car, а затем заставить конструктор Ferrari вызывать setEngine() и передавать экземпляр TurboEngine.

Ответ принят как подходящий

Шаблон «Абстрактная фабрика» предназначен именно для этой проблемы. 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 должен быть Start, а не StartEngine

если вы хотите наложить внутреннюю структуру (например, иметь только один движок), вам понадобится другой движок абстрактного / базового класса, который может быть специализирован.

затем вы можете построить спортивный автомобиль из частей, установив член m_engine на спортивный подкласс Engine и др.

Обновлено: обратите внимание, что в реальном мире турбокомпрессор не является частью двигателя, это надстройка к двигателю с собственным интерфейсом управления ... Но если вы хотите включить такие вещи в свой двигатель Ferrari, это нормально, просто модифицируйте подкласс SportsCar, чтобы превратить ваш базовый двигатель в TurboEngine

но при моделировании было бы лучше держать компоненты отдельно - таким образом вы можете модернизировать свой турбокомпрессор (например, с двойным впуском против одинарного) без замены всего двигателя!

@ vg1890: см. правки; турбокомпрессор не является частью двигателя, это другой компонент, который работает с двигателем.

Steven A. Lowe 11.11.2008 00:35

В зависимости от семантики вашего конкретного языка это можно сделать несколькими способами. Первой моей мыслью было бы создать защищенный конструктор:

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, чтобы иметь TurboEngine, если TurboEngine является подклассом Engine. Вы можете просто указать экземпляр TurboEngine в качестве двигателя для вашего Ferrari. Вы даже можете установить дизельный двигатель в свой Ferrari. Все они просто двигатели.

У машины есть двигатель. TurboEngine - это двигатель. Автомобиль может иметь TurboEngine, DieselEngine или FlintstonesEngine. Все они двигатели.

Если вы хотите ограничить тип Engine в подклассе Car (без LawnMowerEngine в SportsCar), вы можете оставить его объявленным как 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();

У вас есть дженерики на вашем языке? В 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).

Вы можете использовать универсальные шаблоны 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, прежде чем вы вызываете эти методы, но это то, что вы получаете. Не зная, для чего на самом деле стоит эта машина, я не могу придумать ни одной причины, по которой у двигателя и турбомотора не может быть один и тот же интерфейс - есть ли действительно новые методы, которые поддерживает турбо, или просто делает это? то же самое по-разному - но я полагаю, что эта метафора рано или поздно развалится.

Другие вопросы по теме