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 MammalExt开发者_运维知识库ended : 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 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.
I think it's a good use case for the Decorator pattern. The solution you're thinking certainly goes in that direction.
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>();
}
精彩评论