Question

There is a swarm of objects. When a new unit of certain kind appears on the frame, the swarm integrates this object by calling some add_new_unit method. Above the swarm is a controller abstraction, which handles multiple swarms of different types and the interaction with an image processing abstraction that detects units on the frame. Above the controller is a GUI layer.

I want to add a new widget to the GUI every time a new unit is added to the swarm. Essentially, I need to call some method of GUI abstraction object from the swarm, but there is the controller object in the hierarchy between them. Moreover, I want to be able to update this widget when some methods of unit are called (i.e. move the position of the widget together with unit's position)

Going top-to-bottom: GUI knows only about controller, controller knows only about swarm and swarm knows only about its units. I tried to decouple everything as much as possible and isolate every layer. Basically now I am trying to make a way to communicate the information from the bottom (from a unit, i. e. its position, speed and other status parameters) all the way to the top, when some events on a unit or swarm levels happen. I want to keep controller independent of GUI as much as possible.

Is there a way to achieve the above-stated goal without coupling objects from the bottom to the top - i. e. without letting a unit know about a swarm, or without letting a controller know about GUI?

I am thinking about Observer that will report to controller, which fires some handler method every time it observes add_new_unit of the swarm called. But in this case do I need to create Observer that will report to GUI, that will observe `controller``s observer? Something tells me there should be a way to do that which does not involve so much redundancy.

The language in question is Python 3.7

Here is some code to demonstrate - I want GUI.add_new_widget_for_unit to be called every time Swarm.add_new_unit is called, and later modify that widget when some methods of Unit are called:

class Unit:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Swarm:
    def __init__(self):
        self.units = []

    def update(self, units):
        """
        Some updating/tracking logic here...
        """
        if new_units_on_image:
             new_unit = Unit(*params)
             self.add_new_unit(new_unit)

    def add_new_unit(self, unit)
        self.units.append(unit)

class Controller:
    def __init__(self):
        self.swarm = Swarm()
        self.image_processing = ImageProcessing()

    def update(self):
        units_on_image = self.image_processing.update()
        self.swarm.update(units_on_image)

class GUI:
    def __init__(self):
        self.controller = Controller()

    def update(self):
        """ 
        Gets updated by some timer
        """
        self.controller.update()

    def add_new_widget_for_unit(self, parameters_of_unit):
        """
        Adding widget with certain parameters from the unit
        """
Était-ce utile?

La solution

You can use the generic Observer as suggested by the other answer. But consider this:

Going top-to-bottom: GUI knows only about controller, controller knows only about swarm and swarm knows only about its units. I tried to decouple everything as much as possible and isolate every layer

and

I want to add a new widget to the GUI every time a new unit is added to the swarm

You suggested design and your requirement are not well aligned. You don't have a generic use case, only a single specific one. You could decide some nuances, like for example when to signal your observers, according to this specific case. There is still de facto coupling between your layers, and there will be until you have several front-ends.

So it is not that strict to use the generic Observer pattern at all layers. You could instead - and when there are details of implementation required by the specific case it may even be desirable - inject the specific callback "addGuiWidget", or reference to some GUI-level object which would add the widget. In a statically typed language you would have to introduce interface for injecting an object, but for python even that is not necessary.

PS: "inject the specific callback" looks approximately like (I could make some mistake in python syntax):

class Unit:
    def set_add_widget_callback(self, callback):
        self.add_widget_callback = callback

    def add_widget(self):
        assert(self.add_widget_callback is not None)
        self.add_widget_callback(self.foo) # passing parameters

class Swarm:
    def set_add_widget_callback(self, callback):
        self.swarm.add_widget_callback = callback

    def add_new_unit(self, unit)
        unit.set_add_widget_callback(self.add_widget_callback)
        ...

class Controller:
    def set_add_widget_callback(self, callback):
        self.swarm.set_add_widget_callback(callback)

class GUI:
    def __init__(self):
        self.controller = Controller()
        self.controller.set_add_widget_callback(self.add_new_widget_for_unit)

PS2:

Moreover, I want to be able to update this widget when some methods of unit are called (i.e. move the position of the widget together with unit's position)

Now it looks like you rather should pass the reference to the GUI object. Or you could make an simplified adapter of it.

Autres conseils

Have a look at the Observer pattern. Your top layers should be able to subscribe to events exposed by the lower layers. Then the lower layers can provide notification, send messages or transmit data by firing events. The lower layers will have no idea who they're talking to.

This article describes a method involving translating Java's Observable's objects to Python. Anecdotally, this seems like overkill. You ought to be able to simply hand your lower-level object a higher-order function that it can call to notify the upper layer.

You can also try the Command Pattern.

Licencié sous: CC-BY-SA avec attribution
scroll top