tags:

views:

59

answers:

4

I'm working in a C# codebase that has a class hierarchy:

class Animal { Animal prey; }
class Mammal : Animal { Mammal[] livesAmicablyWith; }
class Lion : Mammal { }

Forgive the stupid example.

I would like to repurpose this class hierarchy for something representable in the same exact object format, but which requires more data. In my ideal world, it would look like this:

class Animal { Animal prey; string moreAnimalData; }
class Mammal : Animal { Mammal[] livesAmicablyWith; string moreMammalData; }
class Lion : Mammal { string moreLionData; }

However, I want to avoid adding members to this existing hierarchy, since they'll be at best wasted space, and at worst a bug-prone distraction.

Also, I need all of the original functionality to continue work! Here's what I was thinking of:

class AnimalExtended : Animal {  }
class MammalExtended : Mammal {  
    public void UseLivesAmicablyWith()
    {
        foreach(Mammal m in livesAmicablyWith)
        {
            if(!(m is MammalExtended)
                throw new Exception();

            // use the MammalExtended
        }
    }
}
class LionExtended : Lion {  }

Any suggestions here?

A: 

I guess it depends on how "standard" you want the additional data to be. You could simply use more polymorphism to achieve the result:

interface IExtendedData
{
    string GetMoreData<T>();
}

class ExtendedAnimal: Animal, IExtendedData
{
    public virtual string GetMoreData<T>()
    { 
        if (typeof(T) == typeof(Animal))
            return "Extra animal data";

        return String.Empty;
    }
}

class ExtendedMammal: Mammal, IExtendedData
{
    public override string GetMoreData<T>()
    { 
        if (typeof(T) == typeof(Mammal))
            return "Extra mammal data";

        return base.GetMoreData<Animal>();
    }
}

class ExtendedLion: Lion, IExtendedData
{
    public override string GetMoreData<T>()
    { 
        if (typeof(T) == typeof(Lion))
            return "Extra lion data";

        return base.GetMoreData<Mammal>();
    }
}

In usage, you could dynamic-cast to IExtendedData to agnostically get at the extra data:

var mammal = getLion(); // Returns a lion...possibly an ExtendedLion, but possibly not
var extended = mammal as IExtendedData;
if (extended != null)
{
    string mammalData = extended.GetMoreData<Mammal>();
    string lionData = extended.GetMoreData<Lion>();
}
jrista
Took the words out of my mouth.
Rob Cooke
@hunter: What kind of a comment is that? This isn't a particularly complicated solution...it explicitly solved his desire to *NOT* modify his existing class hierarchy, and still allows him to choose which type he gets extended data for in a fairly agnostic manner (hence the generics.) Check your attitude at the door man...you won't get much respect here otherwise.
jrista
Okay, guys, take it down a notch or a mod is gonna have to lock a post. Also, jrista, I laugh at your rep. Your rep is as puny as your neckbeard. Rep does not make you a better man than anybody else. Except for Jon Skeet, who really is better than everybody else.
Will
@Will: If you actually were Skeet, the rep comment would be simultaneously funny and intimidating on an exponentially increasing curve as the distance between his rep and mine continued to grow. ;P
jrista
+1  A: 

Essentially what you are trying to handle is multiple inheritance, which of course you can't do in C# (see lots of Q's on Stackoverflow about that).

Your options include:-

Move to interfaces: IAnimal, IMammal, ILion and stop using classes when referring to these items. You can now create the ugly big combo-class but only ever refer to it using IAnimal or an IAnimalExtended so that only the relevant properties are visible. Even better use interfaces that group distinct logical features; maybe ISocial instead of IAnimalExtended where ISocial defines how creatures interact with each other.

Use composition instead of inheritance: LionExtended would expose properties for Feeding and SocialNature that are implemented not in some base Animal class but in smaller classes that deal with just one concern.

Do both: Use interfaces and favor composition over inheritance.

Hightechrider
A: 

I think your chosen approach is sound and will work. I would try to do the type checking (e.g. the mammals added to livesAmicablywith) at the time the values are added (assuming that is encapsulated somehow) rather than at the time they are used. It will make errors much easier to catch.

mdma
A: 

I think it's a good use case for the Decorator pattern. The solution you're thinking certainly goes in that direction.

Thomas Levesque