views:

151

answers:

3

When a GUI is composed of several subcomponents that I treat as individual Views with their own Presenter and Models, is there a pattern for gluing them together? Some subcomponents are persistently on the screen while others would get swapped in and out.

  1. What would be a good factory pattern for instantiating the respective MVP triad for a subcomponent that gets added to the GUI at runtime?
  2. How do you glue the subcomponents with the persistent "container" part of the GUI and with each other? Would there be a "God Presenter" that ties other presenters together?

Update: I'm now shooting for something similar to Eclipse's extension mechanism. Plug-ins register themselves to a global registry for the functionality that they provide. When a functionality such as returning data or rendering a View is needed, the registry is queried and the returned functions are invoked (this is pure JavaScript, so I'm not using interfaces). I'm going with a pure-plug-in approach where everything (even the main View) is a plug-in. I might also use an Event Bus to let the various Presenters communicate agnostically.

Now my more specific question becomes, when a plug-in is about to contribute a View, how should I go about initializing the MVP triad and getting the View rendered into a parent container (outside the module). I probably need to let the View render itself into a container passed from outside and inject the View and Model (if needed) into the Presenter. An alternative would be for the View to return a component that can be placed inside a container, but this would be against my tentative ideal of keeping everything that is GUI-framework-specific inside View implementations. I prefer if the factory/glue mechanism can be framework-agnostic.

OK I'll stop yammering now and wait for some feedback, and then perhaps add more clarifications on where exactly I'm stuck...

A: 

For now, I'm going with this approach:

An extender (an extension implementation that a plug-in exposes) that is contributing a GUI component has a getTriad (will come up with a better name later) method that instantiates, wires and returns a MVP triad. The View has a getComponent method that renders and returns a framework-specific GUI element container that can be added to a parent container (the framework-specific details of which are encapsulated within the parent Views). I'm not letting the View render itself into a container but instead letting the parent View render the child into itself. I think this is better in terms of ensuring child Views don't directly mess with parent Views.

Ates Goral
+1  A: 

I guess that "keeping the GUI-framework-specific inside View implementations" is an overall application-level design choice, rather than an absolute must (at least when you think to "view implementation" as "plugin view implementation").

You could - for example - have a very thin view layer at plugin level, and implement a super-view layer within the parent that calls the plugins: thinking to a system of plugins that all add a column to a table, you could well have the bulk of the view code at parent level ("table") and have your plugins to just pass little more than raw data: you would avoid to repeat yourself and would make your code more flexible and maintainable.

On the other hand, if your plugins provide very different types of functionality that never interact directly (for example if they are the different subsystems of a flight simulator) you will want to keep everything that is related to views at plugin level, so that the parent object would not have to even know what a given plugin deals with, but just place it's returned value somewhere in the GUI.

Other factors that would probably influence your choice are the language and framework (if any) that you are using: in my personal experience, design patterns tend to be far from language-agnostic, as each language (and framework) has its own strengths / weaknesses which make certain choices obvious and certain others very difficult to implement.

Just my 2¢ to the discussion, anyhow! :)

mac
A: 

I think the design pattern you're about is mediator.

I've written a javascript framework that consisted of a mediator.

It works like this:

  • You create a global instance of the mediator,
  • register objects under certain names,
  • use the mediator in your implementation to call methods from registered objects within any of the objects.

If something isn't present - no errors fly around. If there are multiple instances - they all get the call.

This is the basic code for that: (An extract of my code. I will make a jquery plugin including that in a while. If you're willing to use it push me to do it faster ;) )

function Mediator(){

    function log(a){
    try {console.log(a);} catch(e){
        try {opera.postError(a);} catch(e){
            //alert(a);
            }
        }
    }

    var __reg={}; // { "what": [object, ...], ... }     //registers an object
    //what=key that will identify, obj=an object
    this._register = function(what,obj){
        if(__reg[what]===undefined){        
            __reg[what]=[];     
            }
        __reg[what].push(obj);      
        }   //unregisters multiple objects and deletes a key
    this._unregisterAll = function(what){
        if(__reg[what]===undefined){log('Overlord:_unregisterAll - no registers'); return false; }
        __reg[what]=null;
        return true;
        }
    //unregisters a single element key
    this._unregister = function(what){
        if(this._countRegisters()==1){
                __reg[what]=null;
                return true;
            } else { log('Overlord:_unregister - no registers'); return false; }
        }
    //unregisters last added element
    this._unregisterLast = function(what){
        var cnt=this._countRegisters(what);
        if(cnt==0) { log('Overlord:_unregisterLast - no registers'); return false; }
        if(cnt==1) {
                __reg[what]=null;
                return true;
            } else {
                __reg[what][cnt-1]=null;
                return true;
            }
        }

    //returns number of registered items
    this._countRegisters = function(what){
        try{
            return __reg[what].length;
            } catch(e){log(e);
            return 0;
            }
        }   //calls a method from all objects registered under 'what' with an array of parameters. returns true if there was at least one successful call
    this._call = function(what,method,params){
        var occured=false;
        for(var i in __reg[what]) {
            try {
                __reg[what][i][method](params);
                occured=true;
                } catch(e) {log(e);//auto reakcja           
                }
            }
        return occured;
        }
    //does the call, but also returns an array of values retuurned by function
    this._returnAll = function(what,method,params){
        var re=[];
        for(var i in __reg[what]){
            try {
                re.push(__reg[what][i][method](params));
                } catch(e) {log(e);//auto reakcja           
                }           
            }
        return re;
        }

    //runs a method from first object for a given key   
    this._returnFirst = function(what,method,params){
        try {
            return __reg[what][0][method](params);
            } catch(e) {log(e);//auto reakcja
            return null;
            }
        }

    }   
naugtur
@David Murdoch. Thanks for the edit David. Capital letter is used when refering to people with "You" where I live. FYI: You shouldn't edit peoples' answers just because something annoys you[small letter as requested]. Somebody might get offended.
naugtur