Search code examples
pythonpython-3.xobject-oriented-analysis

Manager / Container class, how to?


I am currently designing a software which needs to manage a certain hardware setup.

The hardware setup is as following :

System design

System - The system contains two identical devices, and has certain functionality relative to the entire system.

Device - Each device contains two identical sub devices, and has certain functionality relative to both sub devices.

Sub device - Each sub device has 4 configurable entities (Controlled via the same hardware command - thus I don't count them as a sub-sub device).

What I want to achieve :

I want to control all configurable entities via the system manager (the entities are counted in a serial way), meaning I would be able to do the following :

system_instance = system_manager_class(some_params)
system_instance.some_func(0) # configure device_manager[0].sub_device_manager[0].entity[0]
system_instance.some_func(5) # configure device_manager[0].sub_device_manager[1].entity[1]
system_instance.some_func(8) # configure device_manager[1].sub_device_manager[1].entity[0]

What I have thought of doing :

I was thinking of creating an abstract class, which contains all sub device functions (with a call to a conversion function) and have the system_manager, device_manager and sub_device_manager inherit it. Thus all classes will have the same function name and I will be able to access them via the system manager. Something around these lines :

class abs_sub_device():
    @staticmethod
    def convert_entity(self):
        sub_manager = None
        sub_entity_num = None
        pass

    def set_entity_to_2(entity_num):
        sub_manager, sub_manager_entity_num = self.convert_entity(entity_num)
        sub_manager.some_func(sub_manager_entity_num)


class system_manager(abs_sub_device):
    def __init__(self):
        self.device_manager_list = [] # Initiliaze device list
        self.device_manager_list.append(device_manager())
        self.device_manager_list.append(device_manager())

    def convert_entity(self, entity_num):
        relevant_device_manager = self.device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_device_manage, relevant_entity

class device_manager(abs_sub_device):
    def __init__(self):
        self.sub_device_manager_list = [] # Initiliaze sub device list
        self.sub_device_manager_list.append(sub_device_manager())
        self.sub_device_manager_list.append(sub_device_manager())        

    def convert_entity(self, entity_num):
        relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_sub_device_manager, relevant_entity

class sub_device_manager(abs_sub_device):
    def __init__(self): 
        self.entity_list = [0] * 4

    def set_entity_to_2(self, entity_num):
        self.entity_list[entity_num] = 2
  • The code is for generic understanding of my design, not for actual functionality.

The problem :

It seems to me that the system I am trying to design is really generic and that there must be a built-in python way to do this, or that my entire object oriented look at it is wrong.

I would really like to know if some one has a better way of doing this.


Solution

  • After much thinking, I think I found a pretty generic way to solve the issue, using a combination of decorators, inheritance and dynamic function creation.

    The main idea is as following :

    1) Each layer dynamically creates all sub layer relevant functions for it self (Inside the init function, using a decorator on the init function)

    2) Each function created dynamically converts the entity value according to a convert function (which is a static function of the abs_container_class), and calls the lowers layer function with the same name (see make_convert_function_method).

    3) This basically causes all sub layer function to be implemented on the higher level with zero code duplication.

    def get_relevant_class_method_list(class_instance):
        method_list = [func for func in dir(class_instance) if callable(getattr(class_instance, func)) and not func.startswith("__") and not func.startswith("_")]
        return method_list
    
    def make_convert_function_method(name):
        def _method(self, entity_num, *args):
            sub_manager, sub_manager_entity_num = self._convert_entity(entity_num)
            function_to_call = getattr(sub_manager, name)
            function_to_call(sub_manager_entity_num, *args)        
        return _method
    
    
    def container_class_init_decorator(function_object):
        def new_init_function(self, *args):
            # Call the init function :
            function_object(self, *args)
            # Get all relevant methods (Of one sub class is enough)
            method_list = get_relevant_class_method_list(self.container_list[0])
            # Dynamically create all sub layer functions :
            for method_name in method_list:
                _method = make_convert_function_method(method_name)
                setattr(type(self), method_name, _method)
    
        return new_init_function
    
    
    class abs_container_class():
        @staticmethod
        def _convert_entity(self):
            sub_manager = None
            sub_entity_num = None
            pass
    
    class system_manager(abs_container_class):
        @container_class_init_decorator
        def __init__(self):
            self.device_manager_list = [] # Initiliaze device list
            self.device_manager_list.append(device_manager())
            self.device_manager_list.append(device_manager())
            self.container_list = self.device_manager_list
    
        def _convert_entity(self, entity_num):
            relevant_device_manager = self.device_manager_list[entity_num // 4]
            relevant_entity         = entity_num % 4
            return relevant_device_manager, relevant_entity
    
    class device_manager(abs_container_class):
        @container_class_init_decorator
        def __init__(self):
            self.sub_device_manager_list = [] # Initiliaze sub device list
            self.sub_device_manager_list.append(sub_device_manager())
            self.sub_device_manager_list.append(sub_device_manager())    
            self.container_list = self.sub_device_manager_list
    
        def _convert_entity(self, entity_num):
            relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
            relevant_entity         = entity_num % 4
            return relevant_sub_device_manager, relevant_entity
    
    class sub_device_manager():
        def __init__(self): 
            self.entity_list = [0] * 4
    
        def set_entity_to_value(self, entity_num, required_value):
            self.entity_list[entity_num] = required_value
            print("I set the entity to : {}".format(required_value))
    
    # This is used for auto completion purposes (Using pep convention)
    class auto_complete_class(system_manager, device_manager, sub_device_manager):
        pass
    
    
    system_instance = system_manager() # type: auto_complete_class
    system_instance.set_entity_to_value(0, 3)
    

    There is still a little issue with this solution, auto-completion would not work since the highest level class has almost no static implemented function. In order to solve this I cheated a bit, I created an empty class which inherited from all layers and stated to the IDE using pep convention that it is the type of the instance being created (# type: auto_complete_class).