=head1 NAME
Object::InsideOut - Comprehensive inside-out object support module
=head1 VERSION
This document describes Object::InsideOut version 3.96
=head1 SYNOPSIS
package My::Class; {
use Object::InsideOut;
# Numeric field
# With combined get+set accessor
my @data
:Field
:Type(numeric)
:Accessor(data);
# Takes 'INPUT' (or 'input', etc.) as a mandatory parameter to ->new()
my %init_args :InitArgs = (
'INPUT' => {
'Regex' => qr/^input$/i,
'Mandatory' => 1,
'Type' => 'numeric',
},
);
# Handle class-specific args as part of ->new()
sub init :Init
{
my ($self, $args) = @_;
# Put 'input' parameter into 'data' field
$self->set(\@data, $args->{'INPUT'});
}
}
package My::Class::Sub; {
use Object::InsideOut qw(My::Class);
# List field
# With standard 'get_X' and 'set_X' accessors
# Takes 'INFO' as an optional list parameter to ->new()
# Value automatically added to @info array
# Defaults to [ 'empty' ]
my @info
:Field
:Type(list)
:Standard(info)
:Arg('Name' => 'INFO', 'Default' => 'empty');
}
package Foo; {
use Object::InsideOut;
# Field containing My::Class objects
# With combined accessor
# Plus automatic parameter processing on object creation
my @foo
:Field
:Type(My::Class)
:All(foo);
}
package main;
my $obj = My::Class::Sub->new('Input' => 69);
my $info = $obj->get_info(); # [ 'empty' ]
my $data = $obj->data(); # 69
$obj->data(42);
$data = $obj->data(); # 42
$obj = My::Class::Sub->new('INFO' => 'help', 'INPUT' => 86);
$data = $obj->data(); # 86
$info = $obj->get_info(); # [ 'help' ]
$obj->set_info(qw(foo bar baz));
$info = $obj->get_info(); # [ 'foo', 'bar', 'baz' ]
my $foo_obj = Foo->new('foo' => $obj);
$foo_obj->foo()->data(); # 86
=head1 DESCRIPTION
This module provides comprehensive support for implementing classes using the
inside-out object model.
Object::InsideOut implements inside-out objects as anonymous scalar references
that are blessed into a class with the scalar containing the ID for the object
(usually a sequence number). For Perl 5.8.3 and later, the scalar reference
is set as B to prevent I modifications to the ID.
Object data (i.e., fields) are stored within the class's package in either
arrays indexed by the object's ID, or hashes keyed to the object's ID.
The virtues of the inside-out object model over the I object
model have been extolled in detail elsewhere. See the informational links
under L"SEE ALSO">. Briefly, inside-out objects offer the following
advantages over I objects:
=over
=item * Encapsulation
Object data is enclosed within the class's code and is accessible only through
the class-defined interface.
=item * Field Name Collision Avoidance
Inheritance using I classes can lead to conflicts if any classes
use the same name for a field (i.e., hash key). Inside-out objects are immune
to this problem because object data is stored inside each class's package, and
not in the object itself.
=item * Compile-time Name Checking
A common error with I classes is the misspelling of field names:
$obj->{'coment'} = 'Say what?'; # Should be 'comment' not 'coment'
As there is no compile-time checking on hash keys, such errors do not usually
manifest themselves until runtime.
With inside-out objects, I hash keys are not used for accessing field
data. Field names and the data index (i.e., $$self) are checked by the Perl
compiler such that any typos are easily caught using S>.
$coment[$$self] = $value; # Causes a compile-time error
# or with hash-based fields
$comment{$$self} = $value; # Also causes a compile-time error
=back
Object::InsideOut offers all the capabilities of other inside-out object
modules with the following additional key advantages:
=over
=item * Speed
When using arrays to store object data, Object::InsideOut objects are as
much as 40% faster than I objects for fetching and setting data,
and even with hashes they are still several percent faster than I objects.
=item * Threads
Object::InsideOut is thread safe, and thoroughly supports sharing objects
between threads using L.
=item * Flexibility
Allows control over object ID specification, accessor naming, parameter name
matching, and much more.
=item * Runtime Support
Supports classes that may be loaded at runtime (i.e., using
S>). This makes it usable from within L,
as well. Also supports additions to class hierarchies, and dynamic creation
of object fields during runtime.
=item * Exception Objects
Object::InsideOut uses L for handling errors in an
OO-compatible manner.
=item * Object Serialization
Object::InsideOut has built-in support for object dumping and reloading that
can be accomplished in either an automated fashion or through the use of
class-supplied subroutines. Serialization using L is also
supported.
=item * Foreign Class Inheritance
Object::InsideOut allows classes to inherit from foreign (i.e.,
non-Object::InsideOut) classes, thus allowing you to sub-class other Perl
class, and access their methods from your own objects.
=item * Introspection
Obtain constructor parameters and method metadata for Object::InsideOut
classes.
=back
=head1 CLASSES
To use this module, each of your classes will start with
S>:
package My::Class; {
use Object::InsideOut;
...
}
Sub-classes (child classes) inherit from base classes (parent classes) by
telling Object::InsideOut what the parent class is:
package My::Sub; {
use Object::InsideOut qw(My::Parent);
...
}
Multiple inheritance is also supported:
package My::Project; {
use Object::InsideOut qw(My::Class Another::Class);
...
}
Object::InsideOut acts as a replacement for the C pragma: It loads the
parent module(s), calls their Cimport()> methods, and sets up the
sub-class's @ISA array. Therefore, you should not S> yourself,
nor try to set up C arrays. Further, you should not use a class's
C array to determine a class's hierarchy: See L"INTROSPECTION"> for
details on how to do this.
If a parent class takes parameters (e.g., symbols to be exported via
L">), enclose them in an array ref
(mandatory) following the name of the parent class:
package My::Project; {
use Object::InsideOut 'My::Class' => [ 'param1', 'param2' ],
'Another::Class' => [ 'param' ];
...
}
=head1 OBJECTS
=head2 Object Creation
Objects are created using the Cnew()> method which is exported by
Object::InsideOut to each class, and is invoked in the following manner:
my $obj = My::Class->new();
Object::InsideOut then handles all the messy details of initializing the
object in each of the classes in the invoking class's hierarchy. As such,
classes do not (normally) implement their own Cnew()> method.
Usually, object fields are initially populated with data as part of the
object creation process by passing parameters to the Cnew()> method.
Parameters are passed in as combinations of S value>> pairs
and/or hash refs:
my $obj = My::Class->new('param1' => 'value1');
# or
my $obj = My::Class->new({'param1' => 'value1'});
# or even
my $obj = My::Class->new(
'param_X' => 'value_X',
'param_Y' => 'value_Y',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
{
'param_Q' => 'value_Q',
},
);
Additionally, parameters can be segregated in hash refs for specific classes:
my $obj = My::Class->new(
'foo' => 'bar',
'My::Class' => { 'param' => 'value' },
'Parent::Class' => { 'data' => 'info' },
);
The initialization methods for both classes in the above will get
S 'bar'>>, C will also get
S 'value'>>, and C will also get
S 'info'>>. In this scheme, class-specific parameters will
override general parameters specified at a higher level:
my $obj = My::Class->new(
'default' => 'bar',
'Parent::Class' => { 'default' => 'baz' },
);
C will get S 'bar'>>, and C will
get S 'baz'>>.
Calling Cnew()> on an object works, too, and operates the same as
calling Cnew()> for the class of the object (i.e., Cnew()>
is the same as Cnew()>).
How the parameters passed to the Cnew()> method are used to
initialize the object is discussed later under L"OBJECT INITIALIZATION">.
NOTE: You cannot create objects from Object::InsideOut itself:
# This is an error
# my $obj = Object::InsideOut->new();
In this way, Object::InsideOut is not an object class, but functions more like
a pragma.
=head2 Object IDs
As stated earlier, this module implements inside-out objects as anonymous,
read-only scalar references that are blessed into a class with the scalar
containing the ID for the object.
Within methods, the object is passed in as the first argument:
sub my_method
{
my $self = shift;
...
}
The object's ID is then obtained by dereferencing the object: C.
Normally, this is only needed when accessing the object's field data:
my @my_field :Field;
sub my_method
{
my $self = shift;
...
my $data = $my_field[$$self];
...
}
At all other times, and especially in application code, the object should be
treated as an I entity.
=head1 ATTRIBUTES
Much of the power of Object::InsideOut comes from the use of I:
I on variables and subroutines that the L module sends to
Object::InsideOut at compile time. Object::InsideOut then makes use of the
information in these tags to handle such operations as object construction,
automatic accessor generation, and so on.
(Note: The use of attributes is not the same thing as
L.)
An attribute consists of an identifier preceded by a colon, and optionally
followed by a set of parameters in parentheses. For example, the attributes
on the following array declare it as an object field, and specify the
generation of an accessor method for that field:
my @level :Field :Accessor(level);
When multiple attributes are assigned to a single entity, they may all appear
on the same line (as shown above), or on separate lines:
my @level
:Field
:Accessor(level);
However, due to limitations in the Perl parser, the entirety of any one
attribute must be on a single line:
# This doesn't work
# my @level
# :Field
# :Accessor('Name' => 'level',
# 'Return' => 'Old');
# Each attribute must be all on one line
my @level
:Field
:Accessor('Name' => 'level', 'Return' => 'Old');
For Object::InsideOut's purposes, the case of an attribute's name does not
matter:
my @data :Field;
# or
my @data :FIELD;
However, by convention (as denoted in the L module), an
attribute's name should not be all lowercase.
=head1 FIELDS
=head2 Field Declarations
Object data fields consist of arrays within a class's package into which data
are stored using the object's ID as the array index. An array is declared as
being an object field by following its declaration with the C
attribute:
my @info :Field;
Object data fields may also be hashes:
my %data :Field;
However, as array access is as much as 40% faster than hash access, you should
stick to using arrays. See L"HASH ONLY CLASSES"> for more information on
when hashes may be required.
=head2 Getting Data
In class code, data can be fetched directly from an object's field array
(hash) using the object's ID:
$data = $field[$$self];
# or
$data = $field{$$self};
=head2 Setting Data
Analogous to the above, data can be put directly into an object's field array
(hash) using the object's ID:
$field[$$self] = $data;
# or
$field{$$self} = $data;
However, in threaded applications that use data sharing (i.e., use
C), the above will not work when the object is shared between
threads and the data being stored is either an array, hash or scalar reference
(this includes other objects). This is because the C must first be
converted into shared data before it can be put into the field.
Therefore, Object::InsideOut automatically exports a method called
Cset()> to each class. This method should be used in class code to put
data into object fields whenever there is the possibility that
the class code may be used in an application that uses L
(i.e., to make your class code B). The Cset()> method
handles all details of converting the data to a shared form, and storing it in
the field.
The Cset()> method, requires two arguments: A reference to the object
field array/hash, and the data (as a scalar) to be put in it:
my @my_field :Field;
sub store_data
{
my ($self, $data) = @_;
...
$self->set(\@my_field, $data);
}
To be clear, the Cset()> method is used inside class code; not
application code. Use it inside any object methods that set data in object
field arrays/hashes.
In the event of a method naming conflict, the Cset()> method can be
called using its fully-qualified name:
$self->Object::InsideOut::set(\@field, $data);
=head1 OBJECT INITIALIZATION
As stated in L"Object Creation">, object fields are initially populated with
data as part of the object creation process by passing S value>>
parameters to the Cnew()> method. These parameters can be processed
automatically into object fields, or can be passed to a class-specific object
initialization subroutine.
=head2 Field-Specific Parameters
When an object creation parameter corresponds directly to an object field, you
can specify for Object::InsideOut to automatically place the parameter into
the field by adding the C attribute to the field declaration:
my @foo :Field :Arg(foo);
For the above, the following would result in C being placed in
C's C field during object creation:
my $obj = My::Class->new('foo' => $val);
=head2 Object Initialization Subroutines
Many times, object initialization parameters do not correspond directly to
object fields, or they may require special handling. For these, parameter
processing is accomplished through a combination of an C
labeled hash, and an C labeled subroutine.
The C labeled hash specifies the parameters to be extracted from
the argument list supplied to the Cnew()> method. Those parameters
(and only those parameters) which match the keys in the C hash are
then packaged together into a single hash ref. The newly created object and
this parameter hash ref are then sent to the C subroutine for
processing.
Here is an example of a class with an I field and an
I field:
package My::Class; {
use Object::InsideOut;
# Automatically handled field
my @my_data :Field :Acc(data) :Arg(MY_DATA);
# ':Init' handled field
my @my_field :Field;
my %init_args :InitArgs = (
'MY_PARAM' => '',
);
sub _init :Init
{
my ($self, $args) = @_;
if (exists($args->{'MY_PARAM'})) {
$self->set(\@my_field, $args->{'MY_PARAM'});
}
}
...
}
An object for this class would be created as follows:
my $obj = My::Class->new('MY_DATA' => $dat,
'MY_PARAM' => $parm);
This results in, first of all, C being placed in the object's
C field because the C key is specified in the C
attribute for that field.
Then, C is invoked with arguments consisting of the object (i.e.,
C) and a hash ref consisting only of S $param }>>
because the key C is specified in the C hash.
C checks that the parameter C exists in the hash ref, and
then (since it does exist) adds C to the object's C field.
=over
=item Setting Data
Data processed by the C subroutine may be placed directly into the
class's field arrays (hashes) using the object's ID (i.e., C):
$my_field[$$self] = $args->{'MY_PARAM'};
However, as shown in the example above, it is strongly recommended that you
use the Lset()|/"Setting Data"> method:
$self->set(\@my_field, $args->{'MY_PARAM'});
which handles converting the data to a shared format when needed for
applications using L.
=item All Parameters
The C hash and the C attribute on fields act as filters that
constrain which initialization parameters are and are not sent to the C
subroutine. If, however, a class does not have an C hash B
does not use the C attribute on any of its fields, then its C
subroutine (if it exists, of course) will get all the initialization
parameters supplied to the Cnew()> method.
=back
=head2 Mandatory Parameters
Field-specific parameters may be declared mandatory as follows:
my @data :Field
:Arg('Name' => 'data', 'Mandatory' => 1);
If a mandatory parameter is missing from the argument list to Cnew()>,
an error is generated.
For C handled parameters, use:
my %init_args :InitArgs = (
'data' => {
'Mandatory' => 1,
},
);
C may be abbreviated to C, and C or C are
synonymous.
=head2 Default Values
For optional parameters, defaults can be specified for field-specific
parameters using either of these syntaxes:
my @data :Field
:Arg('Name' => 'data', 'Default' => 'foo');
my @info :Field :Arg(info) :Default('bar');
If an optional parameter with a specified default is missing from the argument
list to Cnew()>, then the default is assigned to the field when the
object is created (before the C subroutine, if any, is called).
The format for C handled parameters is:
my %init_args :InitArgs = (
'data' => {
'Default' => 'foo',
},
);
In this case, if the parameter is missing from the argument list to
Cnew()>, then the parameter key is paired with the default value and
added to the C argument hash ref (e.g., S 'foo' }>>).
Fields can also be assigned a default value even if not associated with an
initialization parameter:
my @hash :Field :Default({});
my @tuple :Field :Default([1, 'bar']);
Note that when using C, the value must be properly structured Perl
code (e.g., strings must be quoted as illustrated above).
C and C may be abbreviated to C and C
respectively.
=head3 Generated Default Values
It is also possible to I default values on a per object basis by
using code in the C directive.
my @IQ :Field :Default(50 + rand 100);
my @ID :Field :Default(our $next; ++$next);
The above, for example, will initialize the C attribute of each new
object to a different random number, while its C attribute will be
initialized with a sequential integer.
The code in a C specifier can also refer to the object being
initialized, either as C or as C. For example:
my @unique_ID :Field :Default($self->gen_unique_ID);
Any code specified as a default will I have access to the surrounding
lexical scope. For example, this will not work:
my $MAX = 100;
my $MIN = 0;
my @bar :Field
:Default($MIN + rand($MAX-$MIX)); # Error
For anything lexical or complex, you should factor the initializer out into
a utility subroutine:
sub _rand_max :Restricted
{
$MIN + rand($MAX-$MIX)
}
my @bar :Field
:Default(_rand_max);
When specifying a generated default using the C tag inside an C
directive, you will need to wrap the code in a C, and C (but
not C) can be used to access the object being initialized:
my @baz :Field
:Arg(Name => 'baz', Default => sub { $_[0]->biz });
System functions need to similarly be wrapped in C:
my @rand :Field
:Type(numeric)
:Arg(Name => 'Rand', Default => sub { rand });
Subroutines can be accessed using a code reference:
my @data :Field
:Arg(Name => 'Data', Default => \&gen_default);
On the other hand, the above can also be simplified by using the C
directive instead:
my @baz :Field :Arg(baz) :Default($self->biz);
my @rand :Field :Arg(Rand) :Default(rand) :Type(numeric);
my @data :Field :Arg(Data) :Default(gen_default);
Using generated defaults in the C hash requires the use of the same
types of syntax as with the C tag in an C directive:
my %init_args :InitArgs = (
'Baz' => {
'Default' => sub { $_[0]->biz },
},
'Rand' => {
'Default' => sub { rand },
},
'Data' => {
'Default' => \&gen_default,
},
);
=head3 Sequential defaults
In the previous section, one of the examples is not as safe or as convenient
as it should be:
my @ID :Field :Default(our $next; ++$next);
The problem is the shared variable (C) that's needed to track the
allocation of C values. Because it has to persist between calls, that
variable has to be a package variable, except under Perl 5.10 or later where
it could be a state variable instead:
use feature 'state';
my @ID :Field :Default(state $next; ++$next);
The version with the package variable is unsafe, because anyone could then
spoof ID numbers just by reassigning that universally accessible variable:
$MyClass::next = 0; # Spoof the next object
my $obj = MyClass->new; # Object now has ID 1
The state-variable version avoids this problem, but even that version is
more complicated (and hence more error-prone) than it needs to be.
The C directive (which can be abbreviated to C or
C) makes it much easier to specify that an attribute's default value is
taken from a linearly increasing sequence. For instance, the ID example above
could be rewritten as:
my @ID :Field :SequenceFrom(1);
This directive automatically creates a hidden variable, initializes it to the
initial value specified, generates a sequence starting at that initial value,
and then uses successive elements of that sequence each time a default value
is needed for that attribute during object creation.
If the initial value is a scalar, then the default sequence is generated by
by computing C. If it is an object, it is generated by
calling C<< $obj->next() >> (or by calling C if the object doesn't
have a C method).
This makes it simple to create a series of objects with attributes whose
values default to simple numeric, alphabetic, or alphanumeric sequences,
or to the sequence specified by an iterator object of some kind:
my @ID :Field :SeqFrom(1); # 1, 2, 3...
my @ID :Field :SeqFrom('AAA'); # 'AAA', 'AAB', 'AAC'...
my @ID :Field :SeqFrom('A01'); # 'A01', 'A02', 'A03'...
my @ID :Field :SeqFrom(ID_Iterator->new); # ->next, ->next, ->next...
In every other respect a C directive is just like a
C. For example, it can be used in conjunction with the C
directive as follows:
my @ID :Field :Arg(ID) :SeqFrom(1);
However, not as a tag inside the C directive:
my @ID :Field :Arg('Name' => 'ID', 'SeqFrom' => 1) # WRONG
For the C hash, you will need to I sequential
defaults if required:
use feature 'state';
my %init_args :InitArgs = (
'Counter' => {
'Default' => sub { state $next; ++$next }
},
);
=head2 Parameter Name Matching
Rather than having to rely on exact matches to parameter keys in the
Cnew()> argument list, you can specify a regular expressions to be used
to match them to field-specific parameters:
my @param :Field
:Arg('Name' => 'param', 'Regexp' => qr/^PARA?M$/i);
In this case, the parameter's key could be any of the following: PARAM, PARM,
Param, Parm, param, parm, and so on. And the following would result in
C being placed in C's C field during object
creation:
my $obj = My::Class->new('Parm' => $data);
For C handled parameters, you would similarly use:
my %init_args :InitArgs = (
'Param' => {
'Regex' => qr/^PARA?M$/i,
},
);
In this case, the match results in S $data }>> being sent
to the C subroutine as the argument hash. Note that the C
hash key is substituted for the original argument key. This eliminates the
need for any parameter key pattern matching within the C subroutine.
C may be abbreviated to C or C.
=head2 Object Pre-initialization
Occasionally, a child class may need to send a parameter to a parent class as
part of object initialization. This can be accomplished by supplying a
C labeled subroutine in the child class. These subroutines, if
found, are called in order from the bottom of the class hierarchy upward
(i.e., child classes first).
The subroutine should expect two arguments: The newly created
(uninitialized) object (i.e., C), and a hash ref of all the parameters
from the Cnew()> method call, including any additional parameters added
by other C subroutines.
sub pre_init :PreInit
{
my ($self, $args) = @_;
...
}
The parameter hash ref will not be exactly as supplied to Cnew()>, but
will be I into a single hash ref. For example,
my $obj = My::Class->new(
'param_X' => 'value_X',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
'My::Class' => { 'param' => 'value' },
);
would produce
{
'param_X' => 'value_X',
'param_A' => 'value_A',
'param_B' => 'value_B',
'My::Class' => { 'param' => 'value' }
}
as the hash ref to the C subroutine.
The C subroutine may then add, modify or even remove any parameters
from the hash ref as needed for its purposes. After all the C
subroutines have been executed, object initialization will then proceed using
the resulting parameter hash.
The C subroutine should not try to set data in its class's fields or
in other class's fields (e.g., using I methods) as such changes will be
overwritten during initialization phase which follows pre-initialization. The
C subroutine is only intended for modifying initialization
parameters prior to initialization.
=head2 Initialization Sequence
For the most part, object initialization can be conceptualized as proceeding
from parent classes down through child classes. As such, calling child class
methods from a parent class during object initialization may not work because
the object will not have been fully initialized in the child classes.
Knowing the order of events during object initialization may help in
determining when this can be done safely:
=over
=item 1. The scalar reference for the object is created, populated with an
L"Object ID">, and blessed into the appropriate class.
=item 2. L subroutines are called in
order from the bottom of the class hierarchy upward (i.e., child classes
first).
=item 3. From the top of the class hierarchy downward (i.e., parent classes
first), L"Default Values"> are assigned to fields. (These may be
overwritten by subsequent steps below.)
=item 4. From the top of the class hierarchy downward, parameters to the
Cnew()> method are processed for C field attributes and entries
in the C hash:
=over
=item a. L"Parameter Preprocessing"> is performed.
=item b. Checks for L"Mandatory Parameters"> are made.
=item c. L"Default Values"> specified in the C hash are added
for subsequent processing by the C subroutine.
=item d. L is performed.
=item e. L"Field-Specific Parameters"> are assigned to fields.
=back
=item 5. From the top of the class hierarchy downward, L subroutines are called with parameters specified
in the C hash.
=item 6. Checks are made for any parameters to Cnew()> that were
not handled in the above. (See next section.)
=back
=head2 Unhandled Parameters
It is an error to include any parameters to the Cnew()> method that are
not handled by at least one class in the hierarchy. The primary purpose of
this is to catch typos in parameter names:
my $obj = Person->new('nane' => 'John'); # Should be 'name'
The only time that checks for unhandled parameters are not made is when at
least one class in the hierarchy does not have an C hash B
does not use the C attribute on any of its fields B uses an
L subroutine for processing
parameters. In such a case, it is not possible for Object::InsideOut to
determine which if any of the parameters are not handled by the C
subroutine.
If you add the following construct to the start of your application:
BEGIN {
no warnings 'once';
$OIO::Args::Unhandled::WARN_ONLY = 1;
}
then unhandled parameters will only generate warnings rather than causing
exceptions to be thrown.
=head2 Modifying C
For performance purposes, Object::InsideOut I each class's
C hash by creating keys in the form of C for the various
options it handles (e.g., C for C).
If a class has the unusual requirement to modify its C hash during
runtime, then it must renormalize the hash after making such changes by
invoking C on it so that Object::InsideOut
will pick up the changes:
Object::InsideOut::normalize(\%init_args);
=head1 ACCESSOR GENERATION
Accessors are object methods used to get data out of and put data into an
object. You can, of course, write your own accessor code, but this can get a
bit tedious, especially if your class has lots of fields. Object::InsideOut
provides the capability to automatically generate accessors for you.
=head2 Basic Accessors
A I accessor is vary basic: It just returns the value of an object's
field:
my @data :Field;
sub fetch_data
{
my $self = shift;
return ($data[$$self]);
}
and you would use it as follows:
my $data = $obj->fetch_data();
To have Object::InsideOut generate such a I accessor for you, add a
C attribute to the field declaration, specifying the name for the
accessor in parentheses:
my @data :Field :Get(fetch_data);
Similarly, a I accessor puts data in an object's field. The I
accessors generated by Object::InsideOut check that they are called with at
least one argument. They are specified using the C attribute:
my @data :Field :Set(store_data);
Some programmers use the convention of naming I and I accessors
using I and I prefixes. Such I accessors can be
generated using the C attribute (which may be abbreviated to
C):
my @data :Field :Std(data);
which is equivalent to:
my @data :Field :Get(get_data) :Set(set_data);
Other programmers prefer to use a single I accessors that
performs both functions: When called with no arguments, it I, and when
called with an argument, it I. Object::InsideOut will generate such
accessors with the C attribute. (This can be abbreviated to
C, or you can use C or C or C or even
C.) For example:
my @data :Field :Acc(data);
The generated accessor would be used in this manner:
$obj->data($val); # Puts data into the object's field
my $data = $obj->data(); # Fetches the object's field data
=head2 I Accessor Return Value
For any of the automatically generated methods that perform I operations,
the default for the method's return value is the value being set (i.e., the
I value).
You can specify the I accessor's return value using the C
attribute parameter (which may be abbreviated to C). For example, to
explicitly specify the default behavior use:
my @data :Field :Set('Name' => 'store_data', 'Return' => 'New');
You can specify that the accessor should return the I (previous) value
(or C if unset):
my @data :Field :Acc('Name' => 'data', 'Ret' => 'Old');
You may use C, C or C as synonyms for C.
Finally, you can specify that the accessor should return the object itself:
my @data :Field :Std('Name' => 'data', 'Ret' => 'Object');
C may be abbreviated to C, and is also synonymous with C.
=head2 Method Chaining
An obvious case where method chaining can be used is when a field is used to
store an object: A method for the stored object can be chained to the I
accessor call that retrieves that object:
$obj->get_stored_object()->stored_object_method()
Chaining can be done off of I accessors based on their return value (see
above). In this example with a I accessor that returns the I value:
$obj->set_stored_object($stored_obj)->stored_object_method()
the I call stores the new object, returning it as well,
and then the I call is invoked via the stored/returned
object. The same would work for I accessors that return the I
value, too, but in that case the chained method is invoked via the previously
stored (and now returned) object.
If the L module (version 0.12 or later) is available, then
Object::InsideOut also tries to do I with method chaining for
I accessors that don't store/return objects. In this case, the object
used to invoke the I accessor will also be used to invoke the chained
method (just as though the I accessor were declared with
S 'Object'>>):
$obj->set_data('data')->do_something();
To make use of this feature, just add C to the beginning of your
application.
Note, however, that this special handling does not apply to I accessors,
nor to I accessors invoked without an argument (i.e., when used
as a I accessor). These must return objects in order for method chaining
to succeed.
=head2 :lvalue Accessors
As documented in L, an C subroutine
returns a modifiable value. This modifiable value can then, for example, be
used on the left-hand side (hence C) of an assignment statement, or
a substitution regular expression.
For Perl 5.8.0 and later, Object::InsideOut supports the generation of
C accessors such that their use in an C context will set the
value of the object's field. Just add C 1> to the I
accessor's attribute. (C may be abbreviated to C.)
Additionally, C (or its abbreviation C) may be used for a
combined I I accessor. In other words, the following are
equivalent:
:Acc('Name' => 'email', 'lvalue' => 1)
:Lvalue(email)
Here is a detailed example:
package Contact; {
use Object::InsideOut;
# Create separate a get accessor and an :lvalue set accessor
my @name :Field
:Get(name)
:Set('Name' => 'set_name', 'lvalue' => 1);
# Create a standard get_/set_ pair of accessors
# The set_ accessor will be an :lvalue accessor
my @phone :Field
:Std('Name' => 'phone', 'lvalue' => 1);
# Create a combined get/set :lvalue accessor
my @email :Field
:Lvalue(email);
}
package main;
my $obj = Contact->new();
# Use :lvalue accessors in assignment statements
$obj->set_name() = 'Jerry D. Hedden';
$obj->set_phone() = '800-555-1212';
$obj->email() = 'jdhedden AT cpan DOT org';
# Use :lvalue accessor in substituion regexp
$obj->email() =~ s/ AT (\w+) DOT /\@$1./;
# Use :lvalue accessor in a 'substr' call
substr($obj->set_phone(), 0, 3) = '888';
print("Contact info:\n");
print("\tName: ", $obj->name(), "\n");
print("\tPhone: ", $obj->get_phone(), "\n");
print("\tEmail: ", $obj->email(), "\n");
The use of C accessors requires the installation of the L
module (version 0.12 or later) from CPAN. See particularly the section
L for more information.
C accessors also work like regular I accessors in being able to
accept arguments, return values, and so on:
my @pri :Field
:Lvalue('Name' => 'priority', 'Return' => 'Old');
...
my $old_pri = $obj->priority(10);
C accessors can be used in L.
B:
While still classified as I, Perl's support for C
subroutines has been around since 5.6.0, and a good number of CPAN modules
make use of them.
By definition, because C accessors return the I of a field,
they break encapsulation. As a result, some OO advocates eschew the use of
C accessors.
C accessors are slower than corresponding I accessors.
This is due to the fact that more code is needed to handle all the diverse
ways in which C accessors may be used. (I've done my best to
optimize the generated code.) For example, here's the code that is generated
for a simple combined accessor:
*Foo::foo = sub {
return ($$field[${$_[0]}]) if (@_ == 1);
$$field[${$_[0]}] = $_[1];
};
And the corresponding code for an C combined accessor:
*Foo::foo = sub :lvalue {
my $rv = !Want::want_lvalue(0);
Want::rreturn($$field[${$_[0]}]) if ($rv && (@_ == 1));
my $assign;
if (my @args = Want::wantassign(1)) {
@_ = ($_[0], @args);
$assign = 1;
}
if (@_ > 1) {
$$field[${$_[0]}] = $_[1];
Want::lnoreturn if $assign;
Want::rreturn($$field[${$_[0]}]) if $rv;
}
((@_ > 1) && (Want::wantref() eq 'OBJECT') &&
!Scalar::Util::blessed($$field[${$_[0]}]))
? $_[0] : $$field[${$_[0]}];
};
=head1 ALL-IN-ONE
Parameter naming and accessor generation may be combined:
my @data :Field :All(data);
This is I for:
my @data :Field :Arg(data) :Acc(data);
If you want the accessor to be C, use:
my @data :Field :LV_All(data);
If I accessors are desired, use:
my @data :Field :Std_All(data);
Attribute parameters affecting the I accessor may also be used. For
example, if you want I accessors with an C I accessor:
my @data :Field :Std_All('Name' => 'data', 'Lvalue' => 1);
If you want a combined accessor that returns the I value on I
operations:
my @data :Field :All('Name' => 'data', 'Ret' => 'Old');
And so on.
If you need to add attribute parameters that affect the C portion
(e.g., C, C, etc.), then you cannot use C. Fall
back to using the separate attributes. For example:
my @data :Field :Arg('Name' => 'data', 'Mand' => 1)
:Acc('Name' => 'data', 'Ret' => 'Old');
=head1 READONLY FIELDS
If you want to declare a I field (i.e., one that can be initialized
and retrieved, but which doesn't have a I accessor):
my @data :Field :Arg(data) :Get(data);
there is a I for that, too:
my @data :Field :ReadOnly(data);
or just:
my @data :Field :RO(data);
If a I I accessor is desired, use:
my @data :Field :Std_RO(data);
For obvious reasons, attribute parameters affecting the I accessor cannot
be used with read-only fields, nor can C be combined with
C.
As with C, if you need to add attribute parameters that affect the
C portion then you cannot use the C shorthand: Fall back to using
the separate attributes in such cases. For example:
my @data :Field :Arg('Name' => 'data', 'Mand' => 1)
:Get('Name' => 'data');
=head1 DELEGATORS
In addition to autogenerating accessors for a given field, you can also
autogenerate I to that field. A delegator is an accessor that
forwards its call to one of the object's fields.
For example, if your I object has an C field, then you might
need to send all acceleration requests to the I object stored in that
field. Likewise, all braking requests may need to be forwarded to I's
field that stores the I object:
package Car; {
use Object::InsideOut;
my @engine :Field :Get(engine);
my @brakes :Field :Get(brakes);
sub _init :Init(private) {
my ($self, $args) = @_;
$self->engine(Engine->new());
$self->brakes(Brakes->new());
}
sub accelerate {
my ($self) = @_;
$self->engine->accelerate();
}
sub decelerate {
my ($self) = @_;
$self->engine->decelerate();
}
sub brake {
my ($self, $foot_pressure) = @_;
$self->brakes->brake($foot_pressure);
}
}
If the I needs to forward other method calls to its I