2008-11-10 6 views
7

Ich werde versuchen, meine Frage im Zusammenhang mit einem einfachen Beispiel zu stellen ...Wie verwende ich Komposition mit Vererbung?

Nehmen wir an, ich habe eine abstrakte Basisklasse Auto. Auto hat - ein grundlegendes Motorobjekt. Ich habe eine Methode StartEngine() in der abstrakten Auto-Klasse, die den Start der Engine an das Engine-Objekt delegiert.

Wie erlaube ich Unterklassen von Car (wie Ferrari), das Engine-Objekt als eine bestimmte Art von Motor (z. B. TurboEngine) zu deklarieren? Brauche ich eine andere Auto-Klasse (TurboCar)?

Ich erben ein einfaches altes Engine-Objekt und ich kann es nicht als TurboEngine in meinen Auto-Unterklassen re-deklarieren (oder überschreiben).

EDIT: Ich verstehe, dass ich jede Unterklasse der Engine in myEngine Referenz innerhalb meiner Ferrari-Klasse schließen kann ... aber wie kann ich Methoden aufrufen, die nur die TurboEngine ausgesetzt ist? Da myEngine als Basis-Engine vererbt wird, ist keines der Turbo-Elemente enthalten.

Danke!

+0

Ooooh! Wunderbar! Ich wusste nie, dass es ein * Auto-Analog * -Tag in SO gab! –

Antwort

9

Das abstrakte Fabrikmuster ist genau für dieses Problem. Google GoF Abstract Factory {Ihre bevorzugte Sprache}

Im Folgenden beachten Sie, wie Sie entweder die Betonfabriken verwenden, um "vollständige" Objekte (enzo, civic) zu produzieren, oder Sie können sie "Familien" verwandter Objekte erzeugen (CarbonFrame + TurboEngine, WeakFrame + WeakEngine). Letztlich erhält man immer ein Auto-Objekt, das auf beschleunigendes() mit typenspezifischem Verhalten reagiert.


 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(); 
     } 
    } 
0

Es gibt viele Möglichkeiten, wie es getan werden könnte.

Ich würde bevorzugen setEngine() Methode auf Car aufweist, dann den Anruf Ferrari Konstruktor mit setEngine() und in einer Instanz eines TurboEngine passieren.

0

nicht die Interna der Klasse in der Schnittstelle aussetzen - mit anderen Worten, sollte die öffentliche Methode Auto starten, nicht

StartEngine, wenn Sie eine interne Struktur aufzwingen wollen (dh wie wenn Sie nur 1 Engine haben), dann brauchen Sie eine andere abstrakte/Basisklasse Engine, die spezialisiert werden kann.

dann können Sie einen Sportwagen aus Teilen konstruieren, indem das m_engine Mitglieds zu einer sportlichen Unterklasse von Motoreinstellung, et al

EDIT: beachten Sie, dass in der realen Welt, ein Turbolader nicht Teil des Motors ist, Es ist ein Add-On für den Motor, mit einer eigenen Steuerschnittstelle ... Aber wenn Sie solche Dinge in Ihren Ferrari-Motor einbauen möchten, ist das in Ordnung, nur in der SportsCar-Unterklasse, um Ihre Basis-Engine zu einer TurboEngine

zu machen

aber es wäre besser zu modellieren, um die Komponenten getrennt zu halten - auf diese Weise können Sie Ihren Turbolader (Dual-Einlass gegen einzelne Aufnahme zum Beispiel) aufrüsten, ohne den gesamten Motor zu ersetzen!

+0

@ vg1890: siehe Bearbeitungen; Ein Turbolader ist nicht Teil des Motors, es ist eine andere Komponente, die mit dem Motor arbeitet –

1

Abhängig von der jeweiligen Sprachsemantik gibt es mehrere Möglichkeiten.Aus der Manschette wäre mein erster Gedanke, einen geschützten Konstruktor zur Verfügung zu stellen:

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()); 
    } 
} 
2

Es gibt keine Notwendigkeit, eine Unterklasse von Auto zu erhalten, eine Strömungsmaschine zu haben, solange Strömungsmaschine eine Unterklasse von Engine ist. Sie können einfach eine Instanz von TurboEngine als Engine für Ihren Ferrari angeben. Sie könnten sogar eine DieselEngine in Ihren Ferrari einbauen. Sie sind alle nur Motoren.

Ein Auto hat einen Motor. Eine TurboEngine ist eine Engine. Ein Auto kann eine TurboEngine oder eine DieselEngine oder eine FlintstonesEngine haben. Sie sind alle Motoren.

Wenn Sie den Motortyp in Ihrer Auto-Unterklasse (keine LawnMowerEngine in einem SportsCar) einschränken möchten, können Sie ihn als Engine deklarieren und in den Setter-Methoden begrenzen.

Das Auto hat eine Engine-Beziehung beschränkt nicht die anwendbaren Unterklassen der Engine.

2

Sie können immer eine Zusammenfassung verwenden, die geschützt ist. Das öffentliche "Start" wird das geschützte (das wird in der abstrakten Klasse sein) aufrufen. Auf diese Weise sieht der Aufrufer nur die Start() und nicht die 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!!!"); 
    } 

} 

-Der Weg, es zu benutzen:

Car c = new Ferrari(); 
c.Start(); 
1

Ich denke, das funktionieren würde.

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

Haben Sie Generika in Ihrer Sprache? In Java könnte ich das tun:

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() {} 
} 

Ich bin sicher, dass es etwas Ähnliches in C# ist. Sie können dann eine Instanz von Ferrari entweder als Instanz der Ferrari-Unterklasse behandeln (wobei getEngine die TurboEngine zurückgibt) oder als Instanz der Car-Superklasse (wenn getEngine eine Engine zurückgibt).

1

Sie können hier C# Generika verwenden, um zu bekommen, wonach Sie suchen.

Die Unterscheidung Generika ist, dass Ihr Ferrari „weiß“, dass seine Engine ist-ein TurboEngine, während die Car Klasse nichts Neues — wissen, hat nur das EngineType ist-ein 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

Wie ich Ihre (aktualisiert) Frage verstehen, dann werden Sie das Auto-Motor auf die TurboEngine Typ haben zu werfen, wenn Sie TurboEngine Methoden auf sie anrufen möchten. Das führt zu einer Menge Überprüfung, um zu sehen, ob das Auto, das du hast, eine TurboEngine hat, bevor du diese Methoden nennst, aber das ist, was du erhältst. Da ich nicht weiß, wofür dieses Auto eigentlich steht, kann ich mir keinen Grund vorstellen, warum Motor und Turbomotor nicht die gleiche Schnittstelle haben. Gibt es wirklich neue Methoden, die der Turbo unterstützt, oder einfach nur? die gleichen Dinge anders - aber ich denke, dass diese Metapher früher oder später auseinander fallen würde.