I am working on a simple simulation where I would like to change the methods of a class instance at runtime. I am quite new to OOP so I am not sure which approach fits my case best.
I have created a couple of samples with the example being a Cat
class which can turn into a zombie cat at runtime, changing it's behaviour.
class Cat:
def __init__(self, foo):
self.foo = foo
self.is_zombie = False
def turn_to_zombie(self, bar):
self.is_zombie = True
self.zombie_bar = bar
def do_things(self):
if self.is_zombie:
print('Do zombie cat things')
else:
print('Do cat things')
This is the desired behaviour however I would like to separate the Cat
and ZombieCat
methods and skip the if
statement.
class Cat:
def __init__(self, foo):
self. foo = foo
def do_things(self):
print('Do cat things')
def turn_to_zombie(self, bar):
self.bar = bar
self.__class__ = ZombieCat
class ZombieCat(Cat):
def __init__(self, foo, bar):
super().__init__(self, foo)
self.bar = bar
def do_things(self):
print('Do zombie cat things')
This works well but I am not sure if there are any side effects to changing self.__class__
, it seems to be discouraged How dangerous is setting self.__class__ to something else?
class Cat:
def __init__(self, foo):
self.foo = foo
self.strategy = CatStrategy
def do_things(self):
self.strategy.do_things(self)
def turn_to_zombie(self, bar):
self.bar = bar
self.strategy = ZombieCatStrategy
class CatStrategy:
@staticmethod
def do_things(inst):
print('Do cat things')
class ZombieCatStrategy(CatStrategy):
@staticmethod
def do_things(inst):
print('Do zombie cat things')
When googling I came across the strategy pattern. This also works but feels a bit messier than creating a child class. For example to override an additional method when the cat is a zombie it requires changes in 3 places instead of 1.
Feel free to suggest other patterns, I'm sure there are things I have not considered yet.
Edit:
After a helpful answer from @martineau I'd like to add that it would be useful if any references to a Cat
instance are updated when .turn_to_zombie
is called, i.e.
cats_list1 = [cat1, cat2]
cats_list2 = [cat1, cat2]
cats_list1[0].do_things() # -> Do cat things
cats_list1[0].turn_to_zombie('bar')
cats_list2[0].do_things() # -> Do zombie cat things