Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 496
Rohi Avatar asked Apr 12 '18 16:04

Rohi


4 Answers

Does this solve your Problem?

class EndDevice:
    def __init__(self, entities_num):
        self.entities = list(range(entities_num))

    @property
    def count_entities(self):
        return len(self.entities)

    def get_entity(self, i):
        return str(i)


class Device:
    def __init__(self, sub_devices):
        self.sub_devices = sub_devices

    @property
    def count_entities(self):
        return sum(sd.count_entities for sd in self.sub_devices)

    def get_entity(self, i):
        c = 0
        for index, sd in enumerate(self.sub_devices):
            if c <= i < sd.count_entities + c:
                return str(index) + " " + sd.get_entity(i - c)
            c += sd.count_entities
        raise IndexError(i)


SystemManager = Device # Are the exact same. This also means you can stack that infinite

sub_devices1 = [EndDevice(4) for _ in range(2)]
sub_devices2 = [EndDevice(4) for _ in range(2)]
system_manager = SystemManager([Device(sub_devices1), Device(sub_devices2)])

print(system_manager.get_entity(0))
print(system_manager.get_entity(5))
print(system_manager.get_entity(15))
like image 149
MegaIng Avatar answered Oct 25 '22 12:10

MegaIng


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).

like image 45
Rohi Avatar answered Oct 25 '22 13:10

Rohi


I can't think of a better way to do this than OOP, but inheritance will only give you one set of low-level functions for the system manager, so it wil be like having one device manager and one sub-device manager. A better thing to do will be, a bit like tkinter widgets, to have one system manager and initialise all the other managers like children in a tree, so:

system = SystemManager()
device1 = DeviceManager(system)
subDevice1 = SubDeviceManager(device1)
device2 = DeviceManager(system)
subDevice2 = SubDeviceManager(device2)

#to execute some_func on subDevice1
system.some_func(0, 0, *someParams)

We can do this by keeping a list of 'children' of the higher-level managers and having functions which reference the children.

class SystemManager:
  def __init__(self):
    self.children = []
  def some_func(self, child, *params):
    self.children[child].some_func(*params)

class DeviceManager:
  def __init__(self, parent):
    parent.children.append(self)
    self.children = []
  def some_func(self, child, *params):
    self.children[child].some_func(*params)

class SubDeviceManager:
  def __init__(self, parent):
    parent.children.append(self)
    #this may or may not have sub-objects, if it does we need to make it its own children list.
  def some_func(self, *params):
    #do some important stuff

Unfortunately, this does mean that if we want to call a function of a sub-device manager from the system manager without having lots of dots, we will have to define it again again in the system manager. What you can do instead is use the built-in exec() function, which will take in a string input and run it using the Python interpreter:

class SystemManager:
  ...
  def execute(self, child, function, *args):
    exec("self.children[child]."+function+"(*args)")

(and keep the device manager the same)

You would then write in the main program:

system.execute(0, "some_func", 0, *someArgs)

Which would call

device1.some_func(0, someArgs)
like image 37
DarthVlader Avatar answered Oct 25 '22 12:10

DarthVlader


Here's what I'm thinking:

SystemManager().apply_to_entity(entity_num=7, lambda e: e.value = 2)

 

class EntitySuperManagerMixin():
    """Mixin to handle logic for managing entity managers."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # Supports any kind of __init__ call.
        self._entity_manager_list = []

    def apply_to_entity(self, entity_num, action):
        relevant_entity_manager = self._entity_manager_list[index // 4]
        relevant_entity_num = index % 4
        return relevant_entity_manager.apply_to_entity(
            relevant_entity_num, action)


class SystemManager(EntitySuperManagerMixin):

    def __init__(self):
        super().__init__()
        # An alias for _entity_manager_list to improve readability.
        self.device_manager_list = self._entity_manager_list
        self.device_manager_list.extend(DeviceManager() for _ in range(4))


class DeviceManager(EntitySuperManagerMixin):

    def __init__(self):
        super().__init__()
        # An alias for _entity_manager_list to improve readability.
        self.sub_device_manager_list = self._entity_manager_list
        self.sub_device_manager_list.extend(SubDeviceManager() for _ in range(4))


class SubDeviceManager():
    """Manages entities, not entity managers, thus doesn't inherit the mixin."""

    def __init__(self):
        # Entities need to be classes for this idea to work.
        self._entity_list = [Entity() for _ in range(4)]

    def apply_to_entity(self, entity_num, action):
        return action(self._entity_list[entity_num])


class Entity():
    def __init__(self, initial_value=0):
        self.value = initial_value

With this structure:

  • Entity-specific functions can stay bound to the Entity class (where it belongs).
  • Manager-specific code needs to be updated in two places: EntitySuperManagerMixin and the lowest level manager (which would need custom behavior anyway since it deals with the actual entities, not other managers).
like image 33
Brian Rodriguez Avatar answered Oct 25 '22 12:10

Brian Rodriguez