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
    Commented 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
    Commented 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
    Commented 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
    Commented Mar 11, 2014 at 22:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.