I have a class, let's call it Animal, defined in one library.
In a separate library, I want to represent these Animals with colors according to their subtype. For example, a Lion object is red.
I want there for be a default color for any Animal in case a color isn't one defined for it.
I also want there be subtypes of Animal, such as Feline, that can themselves define a default color for supersedes that of generic Animals.
A simple way to achieve something like this would be as follows. (I use Python here to demonstrate, but I believe this to be a language-agnostic problem.)
class Animal:
def __init__(self):
self.color = Black
class Feline(Animal):
def __init__(self):
super.__init__()
self.color = Blue
class Dog(Animal):
pass # self.color == Black
class Cat(Feline):
pass # self.color == Blue
class Lion(Feline):
def __init__(self):
super.__init__()
self.color = Red
Essentially, each Animal will have a color defined by itself or the earliest ancestor that defines it.
However, I don't want the color of an Animal be a field. i.e. the representation of an Animal should be separate from the Animal itself.
So, instead, I am running something more like this:
# animal.py
class Animal:
pass
class Feline(Animal):
pass
class Dog(Animal):
pass
class Cat(Feline):
pass
class Lion(Feline):
pass
# animal_colors.py
default_colors: dict[type, Color] = {
Animal: Black,
Feline: Blue,
Lion: Red,
# Notice how Cat, and Dog aren't defined here
}
def get_color(animal: Animal, lookup_table:dict[type, Color]=default_colors) -> Color:
mro = type(animal).__mro__
assert Animal in mro, "Non Animal class passed to get_color()"
for t in mro:
try:
return lookup_table[t]
except KeyError:
continue
raise RuntimeError("Animal object has no defined color")
This way get_color(my_animal) will return the desired color for an animal.
This, however, simply feels like the wrong approach to me... I don't think I should be meddling with the __mro__ for such a simple use case.
I'm looking for better ways to tackle this problem of essentially having attributes and defaults be defined outside the class hierarchy.
Last thing I want to mention, for my case, it makes sense to have instances of Animal or Feline, which would traditionally be abstract class. i.e. objects should be able to instantiate at every level of the hierarchy.. Perhaps I could have DefaultAnimal and DefaultFeline subtypes instead, if needed.
*Bonus question: I believe this approach breaks Liskhov Substitution Principle (if a Feline is supposed to be Blue, a Lion shouldn't be Red) is there a better way to approach this problem?