4

I have a set of classes from a 3rd party library. These classes use an inheritance structure to share logic. I would like to add a layer of abstraction in the middle of their inheritance tree to add functionality to all of the children (concrete) implementations.

Here is a simplified example of the classes in the 3rd party 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() {}
}

I would like to add the concept of an AnimalWithSuperPower. These types of animals should have 1 additional Property; SuperPower. I would like to be able to have classes like CatWithSuperPower which derive from Cat, AnimalWithSuperPower, & Animal so that I can access all the functionality of those.

Here is the definition of SuperPower:

public enum SuperPower { Invisibility, SuperStrength, XRayVision }

My first idea was to use multiple inheritance. But unfortunately, C# doesn't support multiple base classes.

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

My next attempt uses a combination of inheritance, composition, and generics to try to deliver the functionality of the base classes.

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
}

This solution is not very good (IMO) because of these reasons:

  • SuperCat and SuperCatWithPur aren't actually instances of Cat
  • any method you wish to use from Cat needs to be mirrored in the container class
  • feels kind of messy: AnimalWithSuperPower is an Animal but it also takes an Animal type parameter

I also tried doing it with an extension method but it wasn't any better than the above two attempts:

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
}

If I had control of the 3rd party classes, I could likely do this cleanly by introducing a new class in the middle of the inheritance tree, but I can't

My Question:

How can I model AnimalWithSuperPower so that:

  • instances are considered of types Cat (or appropriate sub-class), Animal, & AnimalWithSuperPower
  • all the methods and properties are available without extra pass-through calls
1

1 Answer 1

6

Why don't you just use interfaces?

If you're concerned about sharing functionality, you can use extension methods to serve as your pseudo-base class. It's not exactly ideal, but it should get what you're looking for done.

Something along the lines of:

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();
}
4
  • In my trivial example code, the only functionality which I desire to share is the SuperPower property. Unfortunately, in my real-world problem, the AnimalWithSuperPower abstract class has several properties and methods which I want inherited by all the children. That being said... I am not sure which I prefer: needing multiple pass-through calls for all childrens' behaviour OR re-declaring the abstract behaviour repeatedly in all children. Which is the lesser of 2 evils? LOL
    – Jesse Webb
    Jul 26, 2013 at 20:52
  • @JesseWebb: Neither. Prefer composition over inheritance. Put all your repeated code in a service and inject that into each animal that needs it.
    – pdr
    Jul 26, 2013 at 22:09
  • 1
    @Cameron: Close, but I wouldn't call IAnimalWithSuperPower. There's nothing to say that every object implementing that interface must be an animal. Stick with IHasSuperPower.
    – pdr
    Jul 26, 2013 at 22:12
  • @pdr: It may be worthwhile to define IHasSuperPower, but it's also necessary to define IAnimalWithSuperPower (and for animals that implement IHasSuperPower to also implement IAnimalWithSuperPower), because for a reference to be usable as both IAnimal and IHasSuperPower, it must be of a type that derives from both.
    – supercat
    Mar 11, 2014 at 22:10

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .