edit: Dec 10: due to comments by chromatic - removed unless ref $class

Object Structural Patterns in Perl.

[Gang of Four: Gamma, Helm, Johnson, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1995]

I have what I believe is an elegant implementation of an Object Structural pattern from [Gang of Four] using Perl. In this case, I want to use the Bridge pattern. Bridge allows the decoupling of an abstraction from its implementation, or possibly several implementations.

In the current example space, my motivation is to allow a Session object to have a Bridge to a Storage and a Serializer object. Thus, the Session object can utilize any method for storing and serializing data associated with a session. In the first example, I will use XML::Simple for the serializer object. I will also give an example using a YAML object. I will also give an example of Storage using a wrapper class around DB_File that utilizes DB_File::Lock to lock the database file.

First, the (abstract) base class for the Bridge pattern.

package CGI::Armature::Bridge; use strict; sub new { my $class = shift; my $object_ref = shift; my $method_map = shift; my $self = { object=>$object_ref, }; $self-{$_} = $method_map->{$_} for keys %$method_map; return bless($self, $class); } 1;

This base class defines a "new" method, and defines an "object" element that contains an object reference to an implementation object. The "$method_map" hash maps the interface that will be common to each derived class to the interface of the specific object reference contained in the $self->{object} element.

package CGI::Armature::Serializer; use strict; use vars '@ISA'; BEGIN { require CGI::Armature::Bridge; @ISA = 'CGI::Armature::Bridge'; } sub serialize { my $self = shift; my $hash = shift; my $method = $self->{serialize}; return $self->{object}->$method($hash); } sub deserialize { my $self = shift; my $data = shift; my $method = $self->{deserialize}; return $self->{object}->$method($data); } 1;

This code defines a Serializer class; it derives directly from Bridge and inherits the "new" method and definition of both the method_map and the contained object reference. The child class defines the interface that forwards method calls to the "object", which contains a reference to an object implementing a serializer interface. The methods "serialize" and "deserialize" each use the respective keys to the method_map stored in $self; the values correspond to the methods defined for the interface of the $self->{object}.

use CGI::Armature::Serializer; use XML::Simple; my $serialize_map = { serialize=>'XMLin', deserialize=>'XMLout', }; my $serializer = CGI::Armature::Serializer->new( XML::Simple->new(suppressempty=>q{}, noattr=>1), $serialize_map, );

This snippet of client code instantiates a Serializer object with a method_map that maps "serialize" with "XMLout" and "deserialize" with "XMLin" using the options given to override the default options of XML::Simple.

use CGI::Armature::Serializer; use YAML; my $serialize_map = { serialize=>'Dump', deserialize=>'Load', }; my $serializer = CGI::Armature::Serializer->new( YAML->new(), $serialize_map, );

This snippet of client code does the same thing using YAML as the serialization interface.

Client code for either serializer object would be identical.

$data = $serializer->serialize($data_hash); $data_hash = $serializer->deserialize($data);

Now for the Storage class.

package CGI::Armature::Storage; use strict; use vars '@ISA'; BEGIN { require CGI::Armature::Bridge; @ISA = 'CGI::Armature::Bridge'; } sub fetch { my $self = shift; my $id = shift; my $method = $self->{fetch}; return $self->{object}->$method($id); } sub store { my $self = shift; my $id = shift; my $value = shift; my $method = $self->{store}; $self->{object}->$method($id, $value); } sub fetch_ids { my $self = shift; my $method = $self->{fetch_ids}; return $self->{object}->$method(); } sub delete { my $self = shift; my $id = shift; my $method = $self->{delete}; $self->{object}->$method($id); } 1;

In general, each class derived from Bridge defines an interface, and forwards the method calls corresponding to each method (which is the same as the key in the method_hash) to the contained objects interface.

Here is the wrapper for DB_File.

package CGI::Armature::Database; use strict; use Fcntl; use DB_File::Lock; use CGI::Armature::PathInfo; my %path_info = CGI::Armature::PathInfo::get_paths(); sub new { my $class = shift; my ($dbfile) = @_; die "need dbfile" unless $dbfile; my $self = { dbfile=>$dbfile, }; return bless($self, $class); } sub create { my $self = shift; my %db_file; my $locking = { mode=>'write', lockfile_name=>"$path_info{sessionpath}/exclusive.lock", }; tie( %db_file, 'DB_File::Lock', "$path_info{sessionpath}/$self->{dbfile}", O_RDWR|O_CREAT, 0600, $DB_HASH, $locking, ) or die "Couldn't tie DB_File $path_info{sessionpath}/$self->{dbf +ile} $!; aborting"; untie %db_file; } sub delete { my $self = shift; my $key = shift; my %db_file; my $locking = { mode=>'write', lockfile_name=>"$path_info{sessionpath}/exclusive.lock", }; tie( %db_file, 'DB_File::Lock', "$path_info{sessionpath}/$self->{dbfile}", O_RDWR, 0600, $DB_HASH, $locking, ) or die "Couldn't tie DB_File $path_info{sessionpath}/$self->{dbf +ile} $!; aborting"; delete $db_file{$key}; untie %db_file; } sub set_value { my $self = shift; die "not enough values for set_value" unless @_ == 2; my ($key, $value) = @_; my %db_file; my $locking = { mode=>'write', lockfile_name=>"$path_info{sessionpath}/exclusive.lock", }; tie( %db_file, 'DB_File::Lock', "$path_info{sessionpath}/$self->{dbfile}", O_RDWR, 0600, $DB_HASH, $locking, ) or die "Couldn't tie DB_File $path_info{sessionpath}/$self->{dbf +ile} $!; aborting"; $db_file{$key} = $value; untie %db_file; } sub get_value { my $self = shift; my $key = shift; my %db_file; my $locking = { mode=>'read', lockfile_name=>"$path_info{sessionpath}/exclusive.lock", }; tie( %db_file, 'DB_File::Lock', "$path_info{sessionpath}/$self->{dbfile}", O_RDONLY, 0600, $DB_HASH, $locking, ) or die "Couldn't tie DB_File $path_info{sessionpath}/$self->{dbf +ile} $!; aborting"; my $value = $db_file{$key}; untie %db_file; return $value; } sub get_keys { my $self = shift; my %db_file; my $locking = { mode=>'read', lockfile_name=>"$path_info{sessionpath}/exclusive.lock", }; tie( %db_file, 'DB_File::Lock', "$path_info{sessionpath}/$self->{dbfile}", O_RDONLY, 0600, $DB_HASH, $locking, ) or die "Couldn't tie DB_File $path_info{sessionpath}/$self->{dbf +ile} $!; aborting"; my @keys = keys %db_file; untie %db_file; return \@keys; } 1;

And a snippet of client code.

use CGI::Armature::Storage; use CGI::Armature::Database; my $storage_map = { fetch=>'get_value', store=>'set_value', fetch_ids=>'get_keys', delete=>'delete', }; my $storage = CGI::Armature::Storage->new( CGI::Armature::Database->new('session_id.db'), $storage_map, ); my $data = $storage->fetch($session_id);

In reply to An Object Structural Design Pattern in Perl by hypochrismutreefuzz

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.