継承を伴う合成を使用するにはどうすればよいですか?
-
07-07-2019 - |
質問
簡単な例に基づいて質問してみます...
抽象基本クラス Car があるとします。車には基本的なエンジン オブジェクトがあります。Car 抽象クラスには、エンジンの開始を Engine オブジェクトに委任するメソッド StartEngine() があります。
Car のサブクラス (Ferrari など) が Engine オブジェクトを特定のタイプのエンジン (TurboEngine など) として宣言できるようにするにはどうすればよいですか?別の Car クラス (TurboCar) が必要ですか?
私はプレーンな古い Engine オブジェクトを継承していますが、Car サブクラスでそれを TurboEngine として再宣言 (またはオーバーライド) することはできません。
編集: Engine のサブクラスを Ferrari クラス内の myEngine 参照に接続できることは理解しました...しかし、TurboEngine のみが公開するメソッドを呼び出すにはどうすればよいでしょうか?myEngine はベース エンジンとして継承されるため、ターボ関連のものは含まれません。
ありがとう!
解決
Abstract Factoryパターンは、まさにこの問題に適しています。 Google GoF Abstract Factory {ご希望の言語}
以下では、具体的なファクトリを使用して「完全」を作成する方法に注意してください。オブジェクト(enzo、civic)またはそれらを使用して「家族」を作成できます。関連オブジェクト(CarbonFrame + TurboEngine、WeakFrame + WeakEngine)。最終的には、常にタイプ固有の動作でaccelerator()に応答する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();
}
}
他のヒント
TurboEngineがEngineのサブクラスである限り、TurboEngineを持つためにCarのサブクラスを指定する必要はありません。 TurboEngineのインスタンスをフェラーリのエンジンとして指定するだけです。ディーゼルエンジンをフェラーリに入れることさえできます。それらはすべて単なるエンジンです。
車にはエンジンがあります。 TurboEngineはエンジンです。車には、TurboEngine、DieselEngine、またはFlintstonesEngineを含めることができます。それらはすべてエンジンです。
Carサブクラスのエンジンのタイプを制限する場合(SportsCarにはLawnMowerEngineはありません)、エンジンとして宣言したままにして、setterメソッドで制限することができます。
車にはエンジンの関係があるため、エンジンの適用可能なサブクラスは制限されません。
保護されている要約をいつでも使用できます。公開の「開始」 protectedを呼び出します(abstractクラスではovverideになります)。これにより、呼び出し元はStartEngine()ではなくStart()のみを表示します。
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
はエンジン
です。
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のパブリックメソッドはStartEngineではなくStartである必要があります
内部構造を課す場合(つまり、エンジンが1つだけの場合)、特殊化できる別の抽象/ベースクラスエンジンが必要です。
その後、m_engineメンバーをEngineなどのスポーティーなサブクラスに設定することにより、パーツからスポーツカーを構築できます
編集:現実の世界では、ターボチャージャーはエンジンの一部ではなく、独自の制御インターフェースを備えたエンジンのアドオンであることに注意してください。しかし、このようなものをフェラーリエンジン、大丈夫、SportsCarサブクラスでアップキャストして、ベースエンジンをTurboEngineにする
しかし、コンポーネントを分離しておく方がモデリングが優れています-そのようにして、エンジン全体を交換せずにターボチャージャーをアップグレードできます(たとえば、デュアルインテークとシングルインテーク)!
それを行うには多くの方法があります。
Car
で setEngine()
メソッドを使用し、 Ferrari
コンストラクターが setEngine()
TurboEngine
のインスタンスを渡します。