How to avoid globals in Perl Tk (Tkx) GUI programming using an MVC model

529 views Asked by At

I have an old and very large Perl Tk GUI application that I'm refactoring to Tkx. I want to split the interface into several packages so I can build up the application UI in a modular manner. Also, I want to keep the View separate from the Model, using a Controller to provide an interface between the two.

It seems to me that the only way to design this is with two huge global variables, one holding the model ($MODEL), and the other holding references to the widgets ($UI) that are spread across many packages. Then, the Controller interfaces the two using a series of commands like the following:

$UI->{'entry_widget'}->configure(-variable=>\$MODEL->{'entry_value'});
$UI->{'button_widget'}->configure(-command=>sub {$MODEL->{'entry_value'} = "New Value"} );

My question is: Is there a better way to design the application which avoids having to use these two big globals ($UI and $MODEL)? Any suggestions would be very welcome.

2

There are 2 answers

1
gangrene On

You're not looking to avoid globals, you're looking to use methods, that is replace $hashref->{data} with $model->data or $self->model->data, where $model or $self, is an argument (or a "singleton" as Axeman demonstrates) passed to a signal-handler/callback/command, not a hash you access directly

You use methods to modify the $model, because methods can refuse to update the model with nonsense/incorrect data, they make sure you're not trying to pay with monopoly-money

Your app will always create a model variable, and a view variable, and connect them (maybe via an intermediary, a controller) through argument passing

They don't have to be actual global variables in the perl sense ( Coping with Scoping ), they can be my $variables and still work fine just the way you're using them now (via closures), and you avoid the problems of http://perl.plover.com/varvarname.html, but you don't get the benefits of smart models that know what kind of fuel they need (diesel or unleaded); and connecting your views to your model is more typing

See also the answers and links from What is Model View Presenter?

1
Axeman On

I think package methods are a way to make something globally available, but not a global variable. So something like this, would work:

package MVC;

use strict;
use warnings;
use Scalar::Util qw<refaddr>;

my %MVCs;

sub _domain { 
    my ( $domain_name, $ref, $value ) = @_;
    my $r = \$MVCs{ $key }{ $domain_name };
    return unless $$r or ref( $value );
    if ( ref $value ) {
        $$r = $value;
    }
    return $$r;
}

sub model      { shift; return _domain( 'model', @_ ); }
sub controller { shift; return _domain( 'controller', @_ ); }
sub view       { shift; return _domain( 'view', @_ ); }

So outside the package, you would simply need to call this:

my $controller = MVC->controller( $self ); 

To get the controller associated to an object.

You could even put some export logic into the accessors, like:

unless ( $ref->can( $domain_name )) { 
    not strict 'refs';
    *{ ref( $ref ) . "::$domain_name" } 
        = sub { _domain( $domain_name, $ref ) }
        ;
}

So you could just simply do this:

$self->view->view_method( @args );