简体   繁体   中英

How to extend an object hierarchy to hold extra data?

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?

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.

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>();
}

I think your chosen approach is sound and will work. I would try to do the type checking (eg 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM