Communication among model classes?

Maciek's Avatar

Maciek

13 Sep, 2011 12:05 AM

The simple interaction flow in the standard RL app (e.g., as per home page diagram) seems to be

view (-> event) -> mediator -> signal/event -> command
    -> model -> signal/event -> mediator -> view

or, with a service,

view (-> event) -> mediator -> signal/event -> command -> service
    (-> possibly some async stuff) -> model -> signal/event -> mediator -> view

This is great 90% of the time, but I have several situations where this breaks down, and I'm not sure what the best way to manage that is.

For example, in part of my application, a model provides a facade of another model. The application modifies the first model, but parts of it only react to the simplified model. Changes to the first model sometimes produce changes in the visible state of the second model (though, naturally, the second model needs to see all the changes in the first model).

As a gross simplification, let's say I had a YearModel and a DecadeModel:

public class YearModel {
    private var _year:int = 2000;
    [Inject] public var yearChanged:YearChangedSignal;
    /** Adds one year to the model; dispatches 'YearChanged' signal */
    public function addYear():void {
        _year++; yearChanged.dispatch(year); 
    }
}

public class DecadeModel {
    private var _year:int = 2000;
    [Inject] public var decadeChanged:DecadeChangedSignal;
    /** Adds one year to the model; disatpches 'DecadeChanged' signal if
         the year has been incremented into a new decade */
    public function addYear():void {
        _year++;
        if (year % 10 == 0) decadeChanged.dispatch('the ' + ((_year % 10) * 10) + 's');
    }
}

Now (assuming that we're not dealing with a toy example and the code is too complex to reasonably put in a single class), the two approaches I've considered so far involve either having a Command modify both models:

class AddYearCommand {
    [Inject] public var yearModel:YearModel;
    [Inject] public var decadeModel:DecadeModel;
    public override function execute():void {
        yearModel.addYear();
        decadeModel.addYear();
    }
}

(kind of ugly), or have the second model listen to changes in the first:

public class DecadeModel {
    private var _year:int = 2000;
    [Inject] public var yearChanged:YearChangedSignal;
    [Inject] public var decadeChanged:DecadeChangedSignal;
    [PostConstruct]
    public function initialize():void { yearChanged.add(onYearChanged); }
    /** Adds one year to the model; disatpches 'DecadeChanged' signal if
         the year has been incremented into a new decade */
    private function onYearChanged(year:int):void {
        _year = year;
        if (year % 10 == 0) decadeChanged.dispatch('the ' + ((_year % 10) * 10) + 's');
    }
}

(also kind of ugly). There's also the unspeakably ugly solution of injecting DecadeModel into YearModel and having YearModel manipulate it as necessary, but that's not even worth discussing.

There is also the possibility of mapping another Command to YearChanged to increment the year specifically for DecadeModel, but that seems excessive.

Any thoughts?

  1. 1 Posted by Maciek on 13 Sep, 2011 12:18 AM

    Maciek's Avatar

    Hmm, so I guess I missed the part where it says "don't listen for framework events in model classes". Perhaps I should be adding additional Commands that listen to the "response" signals/events from the first model and modify the second. Any input is still welcome.

  2. Support Staff 2 Posted by creynders on 13 Sep, 2011 07:24 AM

    creynders's Avatar

    Yes, models shouldn't be listening for framework events, nor should they ever access other models.
    The cleanest solution is to do it with commands. Let a command update both models and/or let commands react to events from the first model to update the second.
    Another possible solution is to add a separate helper class as a middle man that does all the above combined. This can be easier to maintain and sometimes it's more clear too since all middle-man code gets centralized.

  3. 3 Posted by Maciek on 13 Sep, 2011 04:38 PM

    Maciek's Avatar

    Thanks, yeah. I think taking another look at my use cases, I can probably come up with a cleaner way of managing this through either command-updates-two-models or "cascading" commands. Unfortunately, I don't think the shared middleman is a good fit for me at the moment, but it's something to keep in mind for the future.

  4. Support Staff 4 Posted by creynders on 13 Sep, 2011 07:18 PM

    creynders's Avatar

    Daisychaining commands is best to be avoided though. It gets messy real fast when you need to implement changes. It would be better to use a statemachine or a command sequencer like for instance macrobot: https://github.com/Aaronius/robotlegs-utilities-Macrobot
    Granted, the shared middleman is only a manageable solution in some specific cases.

  5. 5 Posted by Maciek on 13 Sep, 2011 10:14 PM

    Maciek's Avatar

    Well, for me, the daisychaining would be more like

    view (-> event) -> mediator -> signal/event -> command -> service
        (-> possibly some async stuff) -> model-1
        [-> signal/event -> command -> model-2... ] (0 or more times)
        -> signal/event -> mediator -> view
    

    To me, this makes more sense than a state machine (I don't think a "super-state" which encompasses all the combinations of possible underlying model states makes sense here). And to me, macrobot almost seems like it's treading on the Context's turf by managing command mappings and invocation--then again, since the example in the blog post is so contrived (not judging--it's really hard to do meaningful examples), it's hard to see exactly what problem it's solving. I don't suppose you have a more realistic example?

  6. Support Staff 6 Posted by creynders on 14 Sep, 2011 06:36 AM

    creynders's Avatar

    Ok, I misunderstood your use of "cascading". As you describe it above that's probably how I would do it too, depending on use case obviously.
    Command mapping and invocation isn't restricted to the context though. For big applications it would be unmanageable to cram all mappings into the startup method. In general I have several commands in which mappings are made.
    You can take a look at an example I made, should it interest you:
    https://github.com/creynders/robotlegs-demo-bootstrap
    It's not a functional example and there's a bunch of things that can be improved - if I ever get the time I'm going to start from scratch - but it'll give a general idea how you can map the various actors depending on tier and functional area.

  7. 7 Posted by Maciek on 15 Sep, 2011 07:11 PM

    Maciek's Avatar

    That's a good point. I guess my application is fairly small in terms of number of models, but rather intricate (it's a reporting framework which translates text-based report definitions into trees of VOs which constitute a report instance which can be visualized with pluggable widgets and can be manipulated at runtime). It's great to have the RL community here to bounce ideas off of, since many aspects of something like this are not addressed terribly well by the simple RL demos.

  8. Support Staff 8 Posted by Ondina D.F. on 02 Nov, 2011 05:05 PM

    Ondina D.F.'s Avatar

    Hi Maciek,
    If you want to continue this discussion, feel free to reopen it. I’m closing the thread now.
    Please open new threads for new issues.
    Thank you for posting.
    Ondina

  9. Ondina D.F. closed this discussion on 02 Nov, 2011 05:05 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac