Suppose I have a parent class P with a method main
that implements the core functionality of the class. main
itself calls a function defined in another module called parent_function
, with signature parent_function(x, y)
.
e.g.
from my_module import parent_function
class P:
def main():
# do something
x = P.calc_x()
y = P.calc_y()
z = P.calc_z()
# execute `parent_function`
parent_function(x,y)
# do more things
Now suppose I have a child class C which differs from P by calling child_function
, with signature child_function(x,y,z)
, instead of parent_function(x,y)
. To call child_function
with its different signature, I have to rewrite the whole of the main method, changing just one line
e.g.
from my_module import child_function
class C(P):
def main():
# do something
x = P.calc_x()
y = P.calc_y()
z = P.calc_z()
# execute `child_function`
child_function(x,y,z)
# do more things
This is a violation of the DRY principle.
How can I redesign the code to avoid rewriting the whole main
method?
Some ideas:
Turn parent_function
, child_function
into methods of P / C, set z
as an instance variable so the signatures of the two methods can be the same. I dislike this solution as in my case it does not make sense for z
to be an instance variable.
Store the output (x,y,z)
into a dictionary, change the signature of parent/child_function
to accept **kwargs
, and ignore unused kwargs in parent_function
.
Rather than changing the parent / child function (which may be library functions, which you can't change), you may create a wrapper for those calls in you class.
def parent_function(x, y):
print("parent", x, y)
def child_function(x, y, z):
print("child", x, y, z)
class P:
@staticmethod
def calc_x():
return 1
@staticmethod
def calc_y():
return 2
@staticmethod
def calc_z():
return 3
@classmethod
def internal_call(cls, args):
parent_function(args[0], args[1])
@classmethod
def get_data(cls):
x = P.calc_x()
y = P.calc_y()
return [x, y]
@classmethod
def main(cls):
args = cls.get_data()
cls.internal_call(args)
class C(P):
@classmethod
def internal_call(cls, args):
child_function(args[0], args[1], args[2])
@classmethod
def get_data(cls):
x = P.calc_x()
y = P.calc_y()
z = P.calc_z()
return [x, y, z]
P.main()
C.main()
So that, you overwrite the way to get input data, and which function to forward it to, but not the overall logic of the main function.