Author image Michael Granger

NAME

Class::Translucent - A base class for translucency

SYNOPSIS

    package My::Class;
    BEGIN {
        use Class::Translucent  ({
            name    => 'sparrow',
            item2   => 'else',
            attr    => { one => 'two', two => 'three', three => 'one' },
            events  => [ 'buy', 'grow', 'sell', 'eat', 'sleep' ],
        });

        use base qw{Class::Translucent};
    }


    sub new {
        my $self = shift;

        return $self->SUPER::new( @_ );
    }

    package main;

    my $o = new My::Class;
    print $o->name;             # Prints 'sparrow'
    $o->name( 'robin' );        # Set the per-object value
    print $o->name;             # Prints 'robin'
    print My::Class->name;      # Prints 'sparrow'

EXPORTS

Nothing by default.

REQUIRES

Carp, Data::Dumper

Actually, Data::Dumper is only required for debugging, so this module will eventually only need Carp.

DESCRIPTION

This is an abstract base class that provides functionality for translucent attributes in its derivatives. A translucent attribute is an attribute which has a class-wide default. A class's attributes are set in a template, from which all class and instance method calls initially get/set their data. However, once an object has stored a value, it loses its translucency, and thereafter returns its own distinct value. For more information about translucency, see Tom Christiansen's excellent OO Tutorial for Class Data in Perl, which can be found at <http://language.perl.com/misc/perltootc.html>.

In order for your class to usefully inherit from Class::Translucent, it needs to tell Class::Translucent about itself via a template. This template should be a hash or hash reference containing keys for all the translucent attributes of your class, along with default values for each one.

There are several methods for defining this template. If you have class data that needs to be accessed before any instances of your class are created, you can pass the template as the argument to the 'use' statement, like so:

    use Class::Translucent ({ attribute => 'defaultValue' });

You can also define a package global named the same thing as the last part of your package (eg., if you class is called HTML::Graphics::Vector, the hash should be %HTML::Graphics::Vector::Vector). When Class::Translucent's constructor is called as a superclass constructor from your class (or one of its parent classes) and it doesn't already have a template registered for your class, it will look for such a hash, and if it is found, use it as the class's template.

In any case, as soon as the template is defined, Class::Translucent auto-generates translucent accessor methods for the attributes you've specified in the template, skipping any that may already be defined.

The constructor returns an empty hashref blessed into the calling class.

TODO: More docs

AUTHOR / PERSON TO BLAME

Michael Granger <ged@FaerieMUD.org> based on ideas from Tom's OO Tutorial for Class Data in Perl (perltootc) by Tom Christiansen.

Copyright (c) 1999, 2000, The FaerieMUD Consortium. All rights reserved.

This module is free software. You may use, modify, and/or redistribute this software under the terms of the Perl Artistic License. (See http://language.perl.com/misc/Artistic.html)

TO DO

  • Do template interpolation in the method template accessor methods instead of in the code generation itself. This would mean that subclassing would not obligate one to returning templates.

  • Add per-class translucent data so that derivative class B's attributes are inherited from superclass A, but remain distinct for each class. Can probably accomplish this by iterating over @{"${class}::ISA"}, and fetching the parent class's hash and merging it with our own.

  • Package-qualify object data members ala Damian Conway's Tie::SecureHash.

  • Better documentation

  • Better test suite (or any test suite?)

BUGS

Unintuitive translucency with complex datatypes

Operations other than a simple set() are ambiguous for complex datatypes. For example, push adds an element to an array -- so if an array attribute is translucent, should pushAttribute() called as an object method push the given value onto a new empty array, or should it make a copy of the class data first, and push the new element onto it?

THe copy-on-write behaviour is the current behaviour, but will need some rigorous testing to make sure it conforms to Perl's do-what-I-mean.

Cluttered BEGIN blocks

In order for class methods to be callable immediately after the use Class::Translucent, the import() function must do the method generation. This requires that the class template be mangled into the argument to use, which is perhaps ugly and unintuitive to some. I can't see any way around it, though, as it has to occur in a BEGIN block in order to guarantee that the generated methods exist before the constructor is called. Otherwise, setting class-wide data must wait until the first instance is created.

"Subroutine 'sub' redefined..." warnings with overridden methods

Accessor methods for translucent attributes are generated at load time, and overriding subs are defined after that. This can cause warnings to be issued when the methods are encountered in the derived class. This problem doesn't exist when the methods are created during the call to the constructor, as the method-generation code won't clobber a method which is already defined, but then you have to guarantee that no method will be called as a class method before the first object is constructed. You could resort to calling the constructor from within the package itself, but ACK! =:) Things will still work as is, but spurious warnings can be confusing for those who don't RTFM.

You can also cause the warnings to disappear by prototyping the methods you wish to override before the use Class::Translucent call, but that's unintuitive.

Reload problems

When redefining a class during a reload, there is no current mechanism for re-generating the accessor methods. There should be some method that can be called which will clear at least the %Classes hash in the closure so a class's template can be reloaded. Perhaps some mechanism for clobbering existing accessors would also be desirable.

Another related problem -- do Perl internals care if a method gets clobbered? Does Perl do caching of method lookups, and, if so, what will happen if the cached method is undefined during the life of the program? Randal Schwartz suggests modifying the @ISA, which will cause cached methods to be discarded, but I haven't yet tested this.

GLOBALS

Configuration Globals

%AccessCheck

Method generation templates for the access-check part of each method. Attributes which are prefixed with a single underscore ('_') are considered 'protected', and the accessor methods generated for it may only be called from within the defining class or one of its derivatives. Attributes which are prefixed with two or more underscores are considered 'private', and may only be accessed from within the defining class itself. All other attributes are considered public, and may be accessed from any package.

The code contained in these templates contain special tokens '%% ATTRIBUTE %%', and '%% CLASS %%', which will be replaced, respectively, with the attribute name and the name of the class for which they are being generated.

%AccessorCode

Method generation templates for the scoping part of the generated methods. Attributes with a leading capital are considered to be of class scope only, and calls to its accessors always modify and/or return the class data. All other attributes are considered translucent, and reference the class data if no specific value has been set for the object in which the accessor was called, or if the accessor method is called as a class rather than an instance method.

The code in these templates can contain the same tokens as the %AccessCheck global, and they are subject to the same substitution at method-generation.

Note that the attribute passed to later chunks of the method will always be a reference to the needed attribute, even if the attribute is already a reference. This is to make it easier later on to modify the attribute without knowing exactly where the thing being modified lives.

%MethodTemplates

Method generation templates for various datatypes, keyed by type. These templates are used to create the meat of the generated accessor methods. The code they contain will be passed a reference to the attribute to operate on in a scalar called $attribute, and the rest of the argument list will be untouched.

The word 'attribute' in the key is replaced in the generated method with the name of the attribute. For example, if you had a key named 'bargleAttribute', an attribute called 'name' would result in a generated method called 'bargleName'. Leading underscores in an attribute name are always translated to the beginning of the method name, so if the attribute above was instead called '__name', then the generated method would be '__bargleName'. Attributes with leading capitalization result in leading capitalization in the generated method name as well. Eg., an attribute called 'Name' would result in a method named 'BargleName', and an attribute called '_Name' would generate a method called '_BargleName'.

The methods in the 'default' key/value pair are given to every datatype, and can be overidden by the more specific datatype key/value pair. This can be used to establish some default behaviour for an accessor, and then override it for specific datatypes.

METHODS

import( \%template )

Autogenerates methods for the calling class. This method can either be called automatically from a use statement, or can be called explicitly. Note that overriding one of the methods provided by this function may result in a 'subroutine redefined' warning, as they won't yet exist when import() is called, typically. This is probably harmless.

importToLevel( $level, \%template )

Autogenerates methods for the class indicated by the stackframe specified by level. This can be useful when you want to override the import() method, but still use Class::Translucent's method generation. Idea borrowed from Exporter's export_to_level().

Constructor Methods

new( @args )

Create and return a new hash reference blessed into your class. If accessors have not already been generated for your class, they will be generated from the constructor. Existing (overridden) methods will be preserved.

Protected Methods

_buildAccessors( $class[, \%template] )

Build translucent data accessor methods for the specified class (package), using the template specified either by the optional second argument, or if the second argument is not passed, the template as defined in the class itself. This template should be synonymous with the last part of the package name, so that the template for My::Derived::Class will be called %My::Derived::Class::Class. This function

_buildMethodCode( $attributeName, $codeTemplate, $package )

Build code for a method specified by the attribute name, with the specified code template and bound for the specified package.

_makeMethodName( $attribute, $methodPrototype )

Make and return a method name for the specified attribute with the specified method name prototype.

Static Methods

AccessCheck( $accessorType )

Return a method generation template for the access-check part of a generated method based on the accessor type specified. The type can be one of 'public', 'protected', or 'private'. See the configuration global of the same name for more information.

AccessorCode( $accessorType )

Return a method generation template for the scoping part of a generated method based on the accessor type specified. If the accessor type is 'class', a template appropriate for static methods is returned. If the specified type is 'instance', then a template appropriate for instance methods is returned. See the configuration global of the same name for more information.

MethodTemplates( $dataType )

Return a hashref of method generation templates for the specified datatype. See the documentation for the MethodTemplate configuration hash for more information.