Как использовать композицию с наследованием? - PullRequest
7 голосов
/ 11 ноября 2008

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

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

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

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

РЕДАКТИРОВАТЬ: Я понимаю, что я могу подключить любой подкласс Engine к ссылке myEngine в моем классе Ferrari ... но как я могу вызвать методы, которые предоставляет только TurboEngine? Поскольку myEngine наследуется как базовый движок, ничего из турбо-содержимого не включено.

Спасибо!

Ответы [ 10 ]

9 голосов
/ 11 ноября 2008

Шаблон абстрактной фабрики как раз для этой проблемы. 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();
        }
    }
2 голосов
/ 11 ноября 2008

Вы всегда можете использовать реферат, который защищен. Публичный «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();
2 голосов
/ 11 ноября 2008

Нет необходимости указывать подкласс Car для использования TurboEngine, если TurboEngine является подклассом Engine. Вы можете просто указать экземпляр TurboEngine в качестве двигателя для вашего Ferrari. Вы даже можете поставить дизельный двигатель в свой Ferrari. Они все просто двигатели.

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

Если вы хотите ограничить тип Engine в своем подклассе Car (без LawnMowerEngine в SportsCar), вы можете оставить его объявленным как Engine и ограничить его в методах установки.

Автомобиль имеет отношение Двигатель не ограничивает применимые подклассы Двигателя.

1 голос
/ 11 ноября 2008

Как я понимаю ваш (обновленный) вопрос, вам нужно будет привести двигатель автомобиля к типу TurboEngine, если вы хотите вызвать TurboEngine методы для него. Это приводит к большой проверке, чтобы увидеть, есть ли у вашей машины TurboEngine, прежде чем вызывать эти методы, но это то, что вы получаете. Не зная, для чего на самом деле стоит этот автомобиль, я не могу вспомнить ни одной причины, по которой у вас не было бы, чтобы двигатель и турбодвигатель имели один и тот же интерфейс - есть ли действительно новые методы, которые поддерживает турбо, или он просто делает одни и те же вещи по-разному - но я думаю, что эта метафора рано или поздно развалится.

1 голос
/ 11 ноября 2008

Вы можете использовать дженерики C #, чтобы получить то, что вы ищете, здесь.

Различие в использовании дженериков заключается в том, что ваш Ferrari "знает", что его Engine является -1005 *, тогда как класс 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!");
    }
}
1 голос
/ 11 ноября 2008

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

1 голос
/ 11 ноября 2008

Я думаю, что это будет работать.

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();
1 голос
/ 11 ноября 2008

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

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());
    }
}
0 голосов
/ 11 ноября 2008

не выставляйте внутренние компоненты вашего класса в интерфейсе - другими словами, публичный метод Car должен быть Start, а не StartEngine

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

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

РЕДАКТИРОВАТЬ: обратите внимание, что в реальном мире турбокомпрессор не является частью двигателя, это дополнение к двигателю с его собственным интерфейсом управления ... Но если вы хотите включить такие вещи в свой Ferrari Engine, все в порядке, просто upcast в подклассе SportsCar, чтобы превратить ваш базовый Engine в TurboEngine

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

0 голосов
/ 11 ноября 2008

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

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

...