1

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
        """
  • Give us some idea what architecture you're using now. You name many different components. Which ones know about each other? – candied_orange Mar 17 at 13:50
  • Thanks for the response! Edited the question, hope it will bring some clarification. I have been reading about various patterns and design principles, but struggle to figure out the solution in practice. I think there is no need to say I'm a very beginner. Also have no people around me who know anything related to programming so ANY advice is very welcome! – Cos_ma Mar 17 at 14:57
  • You need to clarify what you mean by "nice," "good," "right" and "better" (without resorting to tautologies like "best practice"). – Robert Harvey Mar 17 at 15:08
  • Thanks for your comment, hope now it's better. – Cos_ma Mar 17 at 15:16
2

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.

  • I don't completely understand what exactly you mean by "inject the specific callback". I have added some code for the demonstration. Could you please point to where I should add this callback? – Cos_ma Mar 18 at 3:18
  • I have also added an additional requirement to the question - I want to be able to modify the created widget when some methods of Unit are called. Should I still be just adding callbacks? – Cos_ma Mar 18 at 3:28
2

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.

  • What puts me off in the pattern from the article about the Observer pattern that you have linked is that I will have to write somewhere in the code a construct like controller.swarm.add_new_unit, meaning that (as far as I understand it) I have to create an Observer instance in the top GUI object, and to assign it to observe the object two layers below I will inevitably couple GUI to swarm, not just GUI to controller and controller to swarm. I am not sure, but I thought that kind of coupling should be avoided? – Cos_ma Mar 17 at 15:40
  • That's why I think your lower-level object could expose an endpoint that accepts a first-class method delegate. Your higher-lebel object can plug into this endpoint. – Robert Harvey Mar 17 at 15:44
  • 1
    Have a look here: rosettacode.org/wiki/Delegates#Python – Robert Harvey Mar 17 at 15:49
  • 1
    @RobertHarvey it seems to be very overcomplicated, any callable object such as instance method reference or lambda should be enough. – max630 Mar 17 at 20:18
  • @max630: Yes, that should indeed be enough. – Robert Harvey Mar 17 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.