Reputation: 2573
I want to implement a business logic in python, in which changes occur frequently. Updating this business logic should be easy and the old logic should be kept.
My current idea looks like this:
class Item():
def __init__(self):
pass
def __call__(self, company, time):
if time < "2017Q1":
return self.calc_base(company, time)
elif time < "2018Q2":
return self.calc_after2017Q1
else:
return self.calc_after2018Q2
def calc_base(self, company, time):
return income(company, time) + costs(company, time)
@time_after("2017Q1")
def calc_after2017Q1(self, company, time):
intermediate = self.calc_base(company, time)
return max(intermediate, 100_000) #Haircutt due to article xxx
@time_after("2018Q2")
def calc_after2018Q2(self, company, time):
intermediate = self.calc_after2017Q1(company, time)
intermediate -= 20_000 #Correction due to xxx
The idea is to call an instance of this class with a company and a time as parameters. Internally the class would look which business logic is to be used for that time and it would route to the right calculation function. Whenever the logic changes, one simply adds a new function and documents how it differs from previous calculations.
This implementation needs quite some housekeeping with the if .. elif .. else in the self.call function. Can anyone think of a pythonic and smart way of automating this? I was thinking along the lines of the decorators (as put in this example but that are not implemented), where I can specify from which time on the new logic is valid until a next one supersedes it.
Lastly, I feel like I'm reinventing the wheel here, as this is probably a very standard problem. But somehow I seem to lack the right vocabulary to find good results in this direction. I would therefore, also appreciate hints into the right direction.
Upvotes: 0
Views: 700
Reputation: 2182
Well, more OOP approach would be to have an abstract class to define base calc function. Create After201Q1
as class extending from this abstract class and implementing the calc
function. Then create After2018Q2
as class extending from After201Q1
Then write a factory
function to get the implementation based on that time. I don't think comparing time less than to a string is a correct way. I think, directly indexing it is a right way. I have created a sample code. The code would mostly look like the one below. However, you may need few modifications as per your need. But i have demonstrated a basic idea.
from abc import ABC, abstractmethod
from collections import OrderedDict
def income(*args):
return 1
def costs(*args):
return 2
# Abstract class
class Item(ABC):
def calc_base(self, company, time):
print('Base calc_base')
return income(company, time) + costs(company, time)
def calc(self, company, time):
print('Base calc')
intermediate = self.calc_base(company, time)
return self.calc_specific(intermediate, time)
@abstractmethod
def calc_specific(self, company, time):
pass
class After2017Q1(Item):
def calc_specific(self, company, time):
print('Running After2017Q1')
return max(company, 100000) # Haircutt due to article xxx
class After2018Q2(After2017Q1):
def calc_specific(self, company, time):
print('Running After2018Q2')
intermediate = super().calc_specific(company, time)
return intermediate - 20000
def factory(time):
object_factory = OrderedDict([
('2017Q1', After2017Q1()),
('2018Q2', After2018Q2())
])
return object_factory[time]
print(factory('2017Q1').calc(None, None))
print('-'*20)
print(factory('2018Q2').calc(None, None))
You can add any number of sub classes and write only the calc_specific
function and then add it in the factory function so that it will be invoked based on time value.
Upvotes: 1