Smart use of Inheritance and recursiveness when working with tree-structured data in object orientated perl5 programming

When developing an program for managing a series of mah-jong games, I recently tried different techniques to work with tree structures in object oriented perl5 programming. Basically, when working with data-trees, the kind of inheritance you get with object orientation has to be combined with recursiveness for children-objects to be able to find data that is located in parent-objects at an unkown higher level in the tree. Modules like Class::MethodMaker will not be helpful here, because they simply let the children inherit the method, but if the data is stored in the parent (or parents parent, the child is not expected to know), the child will not get the data, only a "empty" method.

Perhaps it is an indication of the power of PERL, but a good AUTOLOAD function conviniently adds everything I want here.

Representation of data

Build a tree of four levels:

When a match object is created, a reference to a player_set must be given. When a round object is created, a reference to a match object must be given. When a game object is created, a reference to a round must be given.

from winds.pm, each round object inherits a (reference to a?) list, @winds, starting with east and going counter-clock-wise: east, north, west, south.

When this tree is filled with data, we should be able to output results in different formats (csv, latex, even xml?) and write functions to answer questions like: give a list of player and number of wins for the 10 longest consequtive wins. Here is the outline of such a function:

What is wrong with class data? Representation of data, take two

Use class data, common for all instances of a class. All matches with the same parent has the same players, they share this info. They can inherit it from their parent. However, the parent then must be a particular object.

For a particular match, $one_match_obj->players() should return ["alice", "bob" ...]

For another match, with another parent, $another_match_obj->players() should return ["Fred", "Barney" ...]

While this can be accomplished by run-time modifying of playerset->players, that's a dirty hack. We want an accessible and correct representation of data.

Therefore, we just use methods!! remove libclass-data-accessor-perl and implement a recursive method instead!

inherting data from objects, not classes, by recursive methods

Consider the tree structure outlined above, and where each level is represented by a class, and objects at each level has a parent who is member of the class above. Objects know nothing about their decendents. Data in the higher level objects can reached by the decendents using a little recursive trick which automatically traverses "the parent object" until the current object is of the right class for that type of data (and all this using a simple function that can be inherited from the top class).

This gives us the following features:

playerset is a top level class, parent of all parents. For its descendents to get its data "players" that is in playerset objects, let them inherit a recursive function that has 'playerset' hadcoded as the class where 'players' data exists. The Descendent will first test if it is of class playerset, and since it it not, $self will "become" its parent, until $self is of the right class, then it will get the data for you (it already has it, it just have to return it).

package playerset;
use oocore;
use vars qw( @ISA );
@ISA = qw (oocore);

# if you are not a playerset object, then recursively look upwards in
# the tree. Yes, here it is determined that players is a property of
# playerset. If you don't like that, then supply your own players()
# instead of inheriting this one :-)

sub players {
  my $self = shift;
  my $value = shift;
  my $maybe_new_self = $self->get_right('playerset');
  unless ($value and (ref($self) ne ref($maybe_new_self))) {
    $maybe_new_self->set_or_return_data('players', $value);
  } else {
    croak("Write access to players denied \n");
  }
}

1;

------------------

package oocore;
use strict;
use Carp;

sub new {
  my $class = shift;
  my $self = {};
  bless($self, $class);
  # if new() is called with an argument, assume it is a reference to parent object, and pass it on
  my $parent_ref = shift;
  if (ref($parent_ref)) {
    $self->_initialize($parent_ref);
  } else {
    $self->_initialize();
  }
}

sub _initialize {
  my $self = shift;
  $self->{debug} = 0;
  $self->{error_log} = "/tmp/" . ref($self) . "-error.log";
  $self->{log_file} = "/tmp/" . ref($self) . ".log";
  $self->{store_state_dir} = '/tmp/';
  my $parent_ref = shift;
  if (ref($parent_ref)) {
    $self->{parent_ref} = $parent_ref;
  }
  return $self;
}

sub get_right {
  my $self = shift;
  my $class_to_be = shift;
  while (ref($self) ne $class_to_be and $self) {
    die "not a ref" unless $self->{parent_ref};
    $self = $self->{parent_ref};
  }
  return $self;
}

sub set_or_return_data {
  my $self = shift;
  my $name = shift;
  my $value = shift;
  if ($value) {
    return $self->{$name} = $value;
  } else {
    return $self->{$name};
  }
}

1;

comments powered by Disqus


Back to the index

Blog roll

R-bloggers, Debian Weekly
Valid XHTML 1.0 Strict [Valid RSS] Valid CSS! Emacs Muse Last modified: oktober 17, 2019