A workflow design with multiple inheritances

Many languages have limitations on multiple inheritances, such as Objective-C, Ruby, Java, because inheriting multiple implementation-oriented classes is prone to diamond inheritance, that is, two-parent classes inherit from the same base class. Then the subclass will contain the contents of two grandparents. Probably you have been always told that do not involve too many superclass when you design a new class. However, multiple inheritances is very helpful in some cases. As long as you keep the image of the inheritance tree in your mind, the multiple inheritances will bring you a lot of benefits. One example is my previous project experience. I was assigned a project to design pipelines for a workflow. The structure of the workflow is easy to understand. If you take a look at the following chart. The background is interesting: Most banks will employ analysts to investigate transactions and customer behaviors for compliance purposes. This is a huge cost for hiring too many analysts, but few analysts will cause investigation quality issue. Therefore, a good and very popular structure of an investigation workflow is as following: There are three levels investigators and a top-level director. Each investigator level has analysts who conduct investigation and lead who conduct quality control of the investigation. A case will be, at the end of the day, either waived which means not suspicious or file SAR(suspicious activity filing). The majority of cases will be waived since the truly suspicious cases are extremely rare. analysts can only provide suggestions on case judgment and lead can decide waive or escalate. Only the director has the authority to decide file SAR, others can either decide waive or escalate. A case can be escalated from analyst to lead or lead to next level analyst. Notice that if level2/3 lead has a different opinion with his own analyst, he needs to send the case to the director for further decision, but for level1 lead, he can make the decision by himself, escalate to level2 analyst or waive. In a word, this structure can properly allocate time for waive cases and truly suspicious cases.

To design a pipeline for this workflow. Firstly, we need to understand the function for each position. In the beginning, the director will assign all cases to level1 analysts and generate a management tree, one lead will manage multiple analysts. the director also needs to generate an investigation table(df) for each analyst. An analyst has the authority to check the table, modify verbiage and conclusion when an analyst finished one case, he needs to send the updated table to his lead and start working on another case. When a lead receives a table from his analyst, he can start working on this case, if he wants to waive the case, he can directly close the case by waiving, if he think this case is suspicious or hard to determine, he can send the table to level2 analyst by the management tree result from director. Similarly, level2/3 analysts and leads follow the same rule as level1. Once they finish a case, they need to fill out their own verbiage and conclusion part on the table. verbiage is investigation description and conclusion is investigation result. Intuitively, analyst, lead and director have their own functions, we can design three different classes for each of them and each class contains own attributes and methods. Is it a good way to organize code? Notice that actually these three positions share some basic methods, such as set verbiage, check table. All of them need to set verbiage on the table and check the table method as well. Suppose we need to modify check table method right now, all three classes codes need to be modified. Moreover, some methods are shared only between analyst and lead, some methods are shared only between lead and director. You should be very careful in which classes need to be modified when a specific method changed. Therefore, a better solution is a design class for methods(tools) as well. An individual will then inherit from both position class and tools class. The inheritance structure is as the following chart.

Now when you need to modify the method request_df(table) from the worker, you only need to modify one method in class Manager_Tools_MixIn instead of modifying two such methods in lead and director classes. When the number of classes increases, the advantage will be more obvious. Let’s check the worker tools class first. Notice the following class is not completed class and can not be used directly.

from shutil import copyfile
import os
class WorkerToolsMixIn():
    """Base class for worker level tools.
    """
    def check_df(self,ID):
        """Check the own investigation table,
           ID is the ID of this employee"""
        return df
 
    def set_verbiage(self,ID):
        """set own verbiage on the table,
           ID is the ID of this employee"""
        return df
 
    def set_conclusion(self,ID):
        """set own conclusion on the table,
           ID is the ID of this employee"""
        return df
    
    def send_df(self,source,desti):
        """send table to manager,
           source is source path,
           desti is destination path"""
        if os.path.exists(source):
            copyfile(source,desti)
        print('Send successfully!')
 
    def request_adjust(self,ID,managerID,words):
        """send request to manager for workload adjustment,
           ID is own ID,
           managerID is manager ID"""
        print('Request adjustment already sent!')

The other tools classes are quite similar, you can check the detailed codes form this link. Now let’s take a look at position class.

from abc import ABCMeta, abstractmethod
class Analyst(metaclass=ABCMeta):
    """Base class for all level analyst.
    Warning: This class should not be used directly. Use derived classes
    instead.
    """
    @abstractmethod
    def __init__(self,ID,name,lead_name,lead_ID,case_list,save_path):
        self.level=None
        self.ID=str(ID)
        self.name=name
        self.lead_name=lead_name
        self.lead_ID=str(lead_ID)
        self.case_list=case_list
        self.save_path=save_path
        self.df_path=os.path.join(self.save_path,self.ID+'.csv')
    
    @abstractmethod
    def send_RFI(self,case_ID,words):
          """send request to manager for further information,
           case_ID provide ID of the case need RFI,
           words is rationale for RFI
           This method is only for analyst which can not be shared with other positions"""
        print('RFI sent successfully!')
    
    @abstractmethod
    def track_RFI(self,case_ID):
          """send request to manager for tracking the RFI,
           case_ID provide ID of the case need RFI,
           This method is only for analyst which can not be shared with other positions"""
        print('RFI track request sent successfully!')

Now let’s see the code for Level1 Analyst class. It will have two superclasses WorkerToolsMixIn and Analyst. Similarly, you can also write a class for Level1_Lead whose superclass is ManagerToolsMixIn and Lead.

class Level1_Analyst(Analyst,WorkerToolsMixIn):
    """ class for all level1 analyst.
    """
    # Rewrite the constructor for abstract class Analyst
    def __init__(self,ID,name,lead_name,lead_ID,case_list,save_path):
        self.level='CL1_Analyst'
        self.ID=str(ID)
        self.name=name
        self.lead_name=lead_name
        self.lead_ID=str(lead_ID)
        self.case_list=case_list
        self.save_path=save_path
        self.df_path=os.path.join(self.save_path,self.ID+'.csv')
    
    # Rewrite the method send_RFI for abstract class Analyst
    def send_RFI(self,case_ID,words):
          """send request to manager for further information,
           case_ID provide ID of the case need RFI,
           words is rationale for RFI
           This method is only for analyst which can not be shared with other positions"""
        print('RFI sent successfully!')
    
    # Rewrite the method track_RFI for abstract class Analyst
    def track_RFI(self,case_ID):
          """send request to manager for tracking the RFI,
           case_ID provide ID of the case need RFI,
           This method is only for analyst which can not be shared with other positions"""
        print('RFI track request sent successfully!')

Now suppose you need to modify a method check_df. You only need to modify that method in WorkerToolsMixIn one time. Then all classes Level1/2/3 Analyst, Level1/2/3 Lead and even director totally 7 classes will automatically inherit this method from WorkerToolsMixIn without any modification. You should know previously if you generate a class for each level position, these methods are in all classes and you have to modify codes from 7 different part! Now no matter how many Level1 Analysts or Level 3 Leads are in our bank, we can generate the tables and all other things for each of them in a second! That’s the advantage of multiple inheritances!

Published by frank xu

I am a data science practitioner. I love math, artificial intelligence and big data. I am looking forward to sharing experience with all data science enthusiasts.

Leave a Reply

%d bloggers like this: