LONG: Object oriented approach in perl



Dear all, especially perl hackers,

please excuse the long posting and the rather general questions, I hope that
I haven't offended the group's charter - or anyone who doesn't like Perl.

I have written a perl module that generates perl source code from a Glade UI 
definition file. Glade is of course the beautiful Gtk UI builder written by
Damon Chaplin.

I don't have much experience of object-oriented programming so I would
appreciate any advice that people could give me about good practice in
the world of object-oriented perl. I have tried to follow the examples in
perltoot and I have 'read' perltootc but I am not sure how much I have 
_really_ understood about the OO apprach in general and OO perl in particular.

The intention is to produce perl classes that construct a UI using Gtk and
can be inherited and extended by the developer, for instance to provide 
signal handlers for the widgets in the UI. It is important that when the UI 
is changed and the UI construction classes are regenerated, the programmer
should not need to change any of her own code in order to use the new UI
classes (with dynamically AUTOLOADed signal handlers for any signal handlers
that have not yet been written). The programmer might want to construct and
show several copies of any of the UIs simultaneously.

At the moment I generate a separate class for each toplevel window/dialog 
that has been defined and all the classes are perl packages in one single
source file. 

If I haven't put you all off yet, do you think that I am doing this in a
'proper' and scalable OO way? I have real problems throwing off my modular 
habits so please tell me if I have failed to really grasp the OO nettle. 

I expect the programmer's signal handlers to be in a simple perl module and 
to be Export()ed so that they are visible at generation time. This is so 
that the UI can function during the generation run. This approach falls down
if any of the signal handlers construct one of the toplevel windows/dialogs 
that are being generated of course. It does however allow a shorter 
development cycle and also dynamic construction of simple UIs from a string
of XML containing a Glade UI definition.

Perhaps it would be better just to expect the programmer to subclass the UI 
and provide the signal handlers in this new subclass and forget about trying 
to run the UI during the generation phase at all. 

If anyone is interested, a development version of the Glade-Perl source code 
generator is available from:
http://freespace.virgin.net/dermot.musgrove/computers/perl/

Thanks in advance you for absolutely any advice that you can offer.

The (snipped) top of one such generated class/package would be
    (in the file Generated/BusForm.pm)
---------------8<-----------------------------
package BusFrame;
require 5.000; use English; use strict 'vars', 'refs', 'subs';

BEGIN {
  # Run-time utilities and vars
  use Glade::PerlRun; 
  # Existing signal handler modules
  use Existing::BusForm_mySUBS;  
  use vars        qw( @ISA $AUTOLOAD %fields %stubs);
  # Tell interpreter who we are inheriting from
  @ISA          = qw( Glade::PerlRun );
}

%fields = (
  # These are the data fields that you can set/get using the dynamic
  # calls provided by AUTOLOAD (and their initial values).
  # eg $class->FORMS($new_value);      sets the value of FORMS
  #    $current_value = $class->FORMS; gets the current value of FORMS
  TOPLEVEL => undef,
  FORM     => undef,
  PACKAGE  => 'BusFrame',
  VERSION  => '0.0.1',
  AUTHOR   => 'Dermot Musgrove <dermot.musgrove\@virgin.net>',
  DATE     => 'Mon Jul 26 18:38:55 BST 1999',
);

%stubs = (
  # These are signal handlers that will cause a message_box to be
  # displayed by AUTOLOAD if there is not already a sub of that name
  # in any module specified in 'use_modules'.

  'about_Form' => undef,
  'destroy_Form' => undef,
  'on_quit1_activate' => undef,
);

sub AUTOLOAD {
  my $self = shift;
  my $type = ref($self)
    or die "$self is not an object";
  my $name = $AUTOLOAD;
  $name =~ s/.*://;       # strip fully-qualified portion

  if (exists $self->{_permitted_fields}->{$name} ) {
    # This allows dynamic data methods - see %fields above
    # eg $class->UI('new_value');
    # or $current_value = $class->UI;
    if (@_) {
      return $self->{$name} = shift;
    } else {
      return $self->{$name};
    }

  } elsif (exists $stubs{$name} ) {
    # This shows dynamic signal handler message_box for %stubs above
    __PACKAGE__->show_skeleton_message(
      $AUTOLOAD."\n (AUTOLOADED by ".__PACKAGE__.")", 
      [$self, @ARG], 
      __PACKAGE__, 
      'pixmaps/Logo.xpm');
    
  } else {
    die "Can't access method `$name' in class $type";

  }
}

sub run {
  my ($class) = @ARG;
  Gtk->init;
  my $window = $class->new;
  $window->TOPLEVEL->show;
  Gtk->main;
}

sub new {
#
# This sub will create the UI window
  my $that  = shift;
  my $class = ref($that) || $that;
  my $self  = {
    _permitted_fields   => \%fields, %fields,
    _permitted_stubs    => \%stubs,  %stubs,
  };
  my ($forms, $widgets, $data, $work);

  #
  # Create a GtkWindow 'BusFrame'

>> BLAH BLAH BLAH - UI construction Perl/Gtk calls

  #
  # Return all forms in the constructed UI
  bless $self, $class;
  $self->FORM($forms->{'BusFrame'});
  $self->TOPLEVEL($self->FORM->{'BusFrame'});
  return $self;

}

---------------8<-----------------------------

This can be subclassed by, for example:
---------------8<-----------------------------
package BusForm_Subclass;
require 5.000; use English; use strict 'vars', 'refs', 'subs';

BEGIN {
    # Import our existing signal handler modules
    use Existing::BusForm_mySUBS;
    # Now import the UI construction class
    use Generated::BusForm;
    use vars       qw( @ISA
                       %fields
                       $AUTOLOAD
                   );
    # Tell interpreter who we are inheriting from
    @ISA         = qw( BusFrame  Existing::BusForm_mySUBS);

    # Inherit the AUTOLOAD dynamic methods from BusForm
    *AUTOLOAD      = \&BusFrame::AUTOLOAD;
}

%fields = (
# Insert any extra data access methods that you want to add to 
#   our inherited super-constructor (or overload)
    USERDATA    => undef,
    VERSION     => '9.9.9',
);

#======================================================================
#=== These are the overloaded class constructors and so on          ===
#======================================================================
sub new {
    my $that  = shift;
    # Allow indirect constructor so that we can call eg. 
    #   $window1 = BusForm_mySUBS->new; # and then
    #   $window2 = $window1->new;
    my $class = ref($that) || $that;

    # Call our super-class constructor to get an object and reconsecrate it
    my $self = bless $that->SUPER::new(), $class;

    # Add our own data access methods to the inherited constructor
    my($element);
    foreach $element (keys %fields) {
        $self->{_permitted_fields}->{$element} = $fields{$element};
    }
    @{$self}{keys %fields} = values %fields;
    return $self;
}

sub run {
    my ($class) = @ARG;
    Gtk->init;                                
    my $window = $class->new;
    $window->USERDATA({
        'Key1'   => 'Value1',
        'Key2'   => 'Value2',
        'Key3'   => 'Value3',
        });
    $window->TOPLEVEL->show;
    Gtk->main;
}

#=======================================================================
#==== Below are overloaded signal handlers                          ====
#=======================================================================

>> BLAH BLAH BLAH

---------------8<-----------------------------

and the whole thing could be run by for example:
  perl -e 'use BusForm_Subclass; BusForm_Subclass->run()';
  
This approach works but I am not sure how 'correct' it is.

If you have read this far, thanks again for any advice that you have.
Regards, Dermot



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]