web-dev-qa-db-ja.com

制御しないクラスに基本クラス(継承)機能を追加する

サードパーティのライブラリからのクラスのセットがあります。これらのクラスは、継承構造を使用してロジックを共有します。継承ツリーの真ん中に抽象化の層を追加して、すべての子(コンクリート)実装に機能を追加したいと思います。

以下は、サードパーティlibのクラスの簡略化した例です。

public interface IAnimal
{
    bool IsMammal { get; }
}

public abstract class Animal : IAnimal
{
    public abstract bool IsMammal { get; }
    public string Name { get; set; }
}

public class Cat : Animal
{
    public override bool IsMammal { get { return true; } }
    public void Pur() {}
}

public class Dog : Animal
{
    public override bool IsMammal { get { return true; } }
    public void Fetch() {}
}

public class Snake : Animal
{
    public override bool IsMammal { get { return false; } }
    public void ShedSkin() {}
}

AnimalWithSuperPowerの概念を追加したいと思います。これらのタイプの動物には、1つの追加プロパティが必要です。 SuperPowerCatWithSuperPowerCat、&AnimalWithSuperPowerから派生するAnimalのようなクラスを使用して、それらのすべての機能にアクセスできるようにしたい。

SuperPowerの定義は次のとおりです。

public enum SuperPower { Invisibility, SuperStrength, XRayVision }

私の最初のアイデアは、多重継承を使用することでした。しかし残念ながら、C#は複数の基本クラスをサポートしていません。

private abstract class AnimalWithSuperPower : Animal
{
    public SuperPower SuperPower { get; set; }
}

// doesn't compile because you can't extend 2 classes
private class DogWithSuperPower : AnimalWithSuperPower, Dog {}

次の試みでは、継承、構成、および総称の組み合わせを使用して、基本クラスの機能を提供しようとします。

private abstract class AnimalWithSuperPower<TAnimalType> : Animal where TAnimalType : IAnimal
{
    public SuperPower SuperPower { get; set; }

    protected readonly TAnimalType Animal;

    protected AnimalWithSuperPower()
    {
        Animal = (TAnimalType) Activator.CreateInstance(typeof(TAnimalType));
    }
}

private class SuperCat : AnimalWithSuperPower<Cat>
{
    public override bool IsMammal { get { return Animal.IsMammal; } }
}

private class SuperCatWithPur : AnimalWithSuperPower<Cat>
{
    public override bool IsMammal { get { return Animal.IsMammal; } }

    public void Pur() // needing duplicate pass-through methods/properties like this is painful :(
    {
        Animal.Pur();
    }
}

private static void ExampleUsage()
{
    var invisibleCat = new SuperCat { SuperPower = SuperPower.Invisibility };
    invisibleCat.Pur(); // doesn't compile - can't access Pur() method because doesn't extend Cat

    var xrayCat = new SuperCatWithPur { SuperPower = SuperPower.XRayVision };
    xrayCat.Pur(); // only works because I exposed the method with the EXACT same signature
}

次の理由により、このソリューションはあまりよくありません(IMO)。

  • SuperCatSuperCatWithPurは実際にはCatのインスタンスではありません
  • Catから使用するメソッドは、コンテナクラスにミラーリングする必要があります
  • 面倒な感じがします:AnimalWithSuperPowerAnimalですが、Animal型パラメーターも必要です

私も拡張メソッドでそれをやってみましたが、それは上記の2つの試みよりも優れていませんでした:

private abstract class AnimalWithSuperPower : Animal
{
    public SuperPower SuperPower { get; set; }
}

private static AnimalWithSuperPower WithSuperPower(this Animal animal, SuperPower superPower)
{
    var superAnimal = (AnimalWithSuperPower) animal;
    superAnimal.SuperPower = superPower;
    return superAnimal;
}

private static void ExampleUsage()
{
    var dog = new Dog { Name = "Max" };
    var superDog = dog.WithSuperPower(SuperPower.SuperStrength);
    superDog.Fetch(); // doesn't compile - superDog isn't an instance of Dog
}

サードパーティのクラスを制御できれば、継承ツリーの真ん中に新しいクラスを導入することでこれをきれいに行うことができたかもしれませんが、私はできません

私の質問:

次のようにAnimalWithSuperPowerをモデル化するにはどうすればよいですか。

  • インスタンスは、タイプCat(または適切なサブクラス)、Animal、&AnimalWithSuperPowerと見なされます
  • すべてのメソッドとプロパティは、追加のパススルー呼び出しなしで使用できます
4
Jesse Webb

なぜインターフェースを使用しないのですか?

機能の共有が心配な場合は、拡張メソッドを使用して疑似基本クラスとして機能させることができます。理想的とは言えませんが、求めていることを実現できるはずです。

以下に沿ったもの:

public interface IAnimalWithSuperPower {
    public SuperPower power { get; set; }
}

public class SuperCat : Cat, IAnimalWithSuperPower {
    public SuperPower power { get; set; }
    public SuperCat() {
        SuperPower = SuperPower.SuperStrength;
    }
}

public static void UseSuperPower(this IAnimalWithSuperPower animal) {
    animal.power.doSomething();
}
6
Cameron