package Class::Capsule; $VERSION = 1.0; use strict; use Data::Dumper; $Data::Dumper::Indent = 1; use Pod::Usage; use WeakRef; # Here's the subtle magic. Object instances are stored in this class- # level hash, keyed by a unique memory address. Object instances are # stored under a single class level hash, keyed by memory address. our %data; =head2 NAME Class::Capsule - Base Encapsulation Class for Objects =head2 DESCRIPTION This class provides a default constructor and automatic set, get, and delete methods. I call these collectively: 'accessors'. The class enforces encapsulation in child classes using the memory address key method (props to Damian Conway). The default constructor, new(), takes a hash of parameters and calls the appropriate set method to set their values, then resets the '_MODIFIED' instance variable to zero. It calls '_init()' before returning if it is provided in the child class. This allows you to do error checking and initialize the object as needed. The class uses AUTOLOAD to provide automatic accessor methods for instance variables. You can provide your own accessor implementations, but you should follow the naming convention. If you violate the naming convention, the default accessors will exist anyway and you lose two important benefits. First, AUTOLOADed accessors enforce a naming convention. Set methods must begin with 'set_'. Get methods must begin with 'get_'. Delete methods must begin with 'del_'. You can, and probably should, override these methods. This has the effect of encouraging a predictable interface. If you chose to name your accessors differently, this benefit is foregone. Second, AUTOLOADed accessors count the number of times the object was modified via 'set_' and 'del_'. This is important for persistence. For instance ;^), you need only save objects to your persistence store if they have been modified. If you choose to override the accessors, you should make sure each set and delete method for which you write implementations calls 'increment__MODIFIED()' within the method. This increments the '_MODIFIED' instance variable to flag the object as a candidate for persistence. Never set '_MODIFIED' unless you are resetting it to zero with 'zero__MODIFIED'. The class level hash approach used to provide encapsulation unfortunately also provides opacity. Introspective methods, like to_string() are provided to compensate for that. The class provides a usage() method which dies with the SYNOPSIS, ARGUMENTS, and OPTIONS from the class POD. Do this instead of dying when the class is misused. =head2 SYNOPSIS #### A child class. package CapsuleChild; use Class::Capsule; @ISA = qw(Class::Capsule); use strict; sub _init { my ( $self ) = @_; $self->usage("A stooge is required!") unless $self->get_stooge; } sub persist { my ( $self ) = @_; print "Saving " . $self->get_package . "\n"; } 1; #### Using the child. # Create the object and set an attribute. my $c = CapsuleChild->new( stooge => 'Moe' ); # Create and set an instance variable. $c->set_marx( 'Harpo' ); # Get an instance variable. print $c->get_stooge . "\n"; # Delete an instance variable. $c->del_stooge; # Get a Data::Dumper serialized object. print $c->to_string; # Get a Data::Dumper of all Class::Capsule objects. print $c->to_string_all; # Instance method. print Class::Capsule::to_string_all(); # Class method. # Get the object package. print $c->get_package . "\n"; # Get the object's keys as an array reference. my $keys = $c->get_keys; print Dumper $keys; # Use the '_MODIFIED' flag for conditional persistence. $c->persist if $c->get__MODIFIED; =cut =head2 METHODS =head2 new() Accepts a hash of arguments and calls the appropriate 'set_' method. Calls $self->_init(). Use _init() to further populate the object and check whether required arguments were passed in. =cut sub new { my ( $caller, %params ) = @_; my $class = ref($caller) || $caller; my $self = bless \my($scalar), $class; $data{$self} = { self => $self }; for ( keys %params ) { my $method = "set_" . $_; $self->$method( $params{$_} ); } $self->zero__MODIFIED; weaken $data{$self}{self}; $self->_init if $self->can( '_init' ); return $self; } # Avert your eyes! Evil approacheth! sub AUTOLOAD { my ( $self, $new ) = @_; our $AUTOLOAD; $AUTOLOAD =~ /.*::(set_|get_|del_)(.+)/; unless ( $2 ) { my $package = "Unknown"; $package = $self->get_package if ref($self); my $error = "AUTOLOAD error:\nEither you're attempting to call a non-existent method ($AUTOLOAD)\nor you're not following the accessor naming convention. Accessors must \nbegin with 'set_', 'get_', or 'del_' in $package."; die $error; } if ( $1 eq 'set_' ) { $data{$self}{$2} = $new; $self->increment__MODIFIED; } elsif ( $1 eq 'del_' ) { delete $data{$self}{$2}; $self->increment__MODIFIED; } return $data{$self}{$2} if exists $data{$self}{$2}; } =head2 increment__MODIFIED() Takes no arguments and returns nothing. Increments the '_MODIFIED' instance variable. This should be called internally by every 'set_' and 'del_' method. Use this to determine whether an object should be saved in the persistence store. =cut sub increment__MODIFIED { my ( $self ) = @_; $data{$self}{_MODIFIED}++; } =head2 zero__MODIFIED() Takes no arguments and returns nothing. Sets the '_MODIFIED' instance variable to zero. Never set '_MODIFIED' unless you are resetting it to zero with this method. =cut sub zero__MODIFIED { my ( $self ) = @_; $data{$self}{_MODIFIED} = 0; } =head2 get_() Takes no arguments. Returns the value of an instance variable. Overriding the 'get_' methods differs from the usual approach. Instead of doing this internally, return $self->{foo}; Do this, return $Class::Capsule::data{$self}{foo}; =head2 set_() Takes a scalar (can be a reference). Sets the instance variable's value and returns the new value. If you override this method, make sure you call 'increment__MODIFIED()' within the method. This increments the '_MODIFIED' instance variable to flag the object as a candidate for persistence: $self->increment__MODIFIED; Overriding the 'set_' methods differs from the usual approach. You should always return the new value. Instead of doing this internally, $self->{foo} = 1; return $self->{foo}; Do this, $Class::Capsule::data{$self}{foo} = 1; return $Class::Capsule::data{$self}{foo}; =head2 del_() Takes no arguments. Deletes the instance variable. If you override this method, make sure you call 'increment__MODIFIED()' within the method. This increments the '_MODIFIED' instance variable to flag the object as a candidate for persistence: $self->increment__MODIFIED; There is really no reason to override the delete method. =head2 get_package() Returns the package name for the object instance. =cut sub get_package { my ( $self ) = @_; return ref( $self ); } =head2 get_keys() Provided to enhance transparency, this returns the current keys as an array reference. You still will not be able to break encapsulation though. :D =cut sub get_keys { my ( $self ) = @_; my @keys; for ( keys %{$data{$self}} ) { next if /self/; push( @keys, $_ ); } return \@keys; } =head2 to_string() Provided to enhance transparency, this returns the serialized object courtesy of Data::Dumper. =cut sub to_string { my ( $self ) = @_; return Dumper $data{$self}; } =head2 to_string_all() Here is a little bonus. This method dumps every object that inherits from Class::Capsule, via Data::Dumper. It can be called as an instance method or Class::Capsule class method. =cut sub to_string_all { my ( $self ) = @_; return Dumper \%data; } =head2 usage() Takes a message string. Dies printing SYNOPSIS, ARGUMENTS, and OPTIONS from the class POD. =cut sub usage { my ( $self, $msg ) = @_; my $filepath = $self->get_package . ".pm"; pod2usage( -input => $filepath, -verbose => 1, -msg => $msg ); } =head2 DEPENDENCIES WeakRef.pm, Pod::Usage.pm =cut =head2 AUTHOR Todd Shoenfelt (aisarosenbaum@gmail.com) =cut 1;