# $Id$
# $Source$
# $Author$
# $HeadURL$
# $Revision$
# $Date$
package design::keywords;
# -------------------------------------------------------------------------- #
# Dot 2.0 Design Specification.
# -------------------------------------------------------------------------- #
1;
__END__
=begin wikidoc
= NAME
design::keywords Dot 2.0 Keywords Specification
= SYNOPSIS
use Dot;
= ATTRIBUTES
== The 'has' keyword.
=for implementation_status implemented
An attribute with no type, a get and a set accessor is described like this:
has foo => ();
or
has 'foo';
This is an attribute with the default type {Any}.
You can also define it with the "yada yada yada" type:
has 'foo' => (isa => "...");
which is syntactic sugar for saying the type is not important,
or just not known yet, choose your interpretation :)
You can use an attribute after it is declared like this:
$self->foo # Get foo's value
$self->set_foo($value) # Set foo to a new value.
== Changing accessor names.
=for implementation_status implemented
Yes, it is possible to change the names of these accessors, or use mutators
instead. The hard way is to set the class options {-getter_prefix} and {-setter_prefix}
manually, the easy way is to use policies. Policies are small modules that
just change the way Dot works by changing class options or defining new
types.
This is an example using the Affordance policy, which is a policy that gets
you the accessor names reccommended by Perl Best Practices.
package MyClass2;
use Dot::Policy::Affordance;
has foo => (isa => '...');
my $self = MyClass2->new();
$self->get_foo();
$self->set_foo("new value");
And here's an example using the Mutator policy, which gives you only one
accessor to do both get and set operations.
package MyClass3;
use Dot::Policy::Mutator;
has foo => (isa => '...');
my $self = MyClass3->new();
# Get and set with the same method
my $value = $self->foo();
$self->foo("new value");
== Attribute Privacy.
=for implementation_status implemented
Attribute privacy is specified with the {is} option:
has 'foo' => (is => 'rw') # rw == set and get accessor.
has 'foo' => (is => 'ro') # ro == only get accessor.
has 'foo' => (is => 'wo') # wo == only set accessor.
has 'foo' => (is => 'xx') # xx == private, no autogenerated accessors.
If you don't want to create accessors for the method
you can including setting {is} to {xx} also prefix the attribute name with an exclamation
point ({!}). This is a convenient shortcut and it resembles how it works in Perl 6.
has '!foo' => (isa => 'String');
== Attribute types and default values.
You can specify which type the attribute is with the {isa} option,
and you can set default values with the {default} option.
has 'foo' => (isa => 'String');
has 'bar' => (isa => 'String', default => 'The quick fox');
Different types has different ways of handling the default
values you give.
=== More about default values.
=for implementation_status implemented
All default values are lazy and not actually set before
they are accessed using the get accessor (or the {__getattr__} method
of all Dot classes, but more on that later).
has 'name' => (isa => 'String', default => 'Cosmo Kramer');
my $myobj = MyClass->new();
$myobj->name(); # <-- default value first initialized here!
If it's set before that, like when it's set by the user via {new()}, it's
never initialized at all:
my $myobj = MyClass->new({name => 'Jerry Seinfeld'})
$myobj->name(); # <-- value is now {Jerry Seinfeld},
# so the default value was never set.
== {-defaults}: Assigning list defaults by using defaults with prefix {-}
=for implementation_status todo default_list_flattening deadline => 2.0
Prefix {-} on the default option is special if {-default} is the last option.
So
has 'list' => (isa => 'Array', -default => @array);
is the same as
has 'list' => (isa => 'Array', default => \@array);
But this won't work:
has 'list' => (-default => @array, isa => 'Hash');
You can even make the array inline:
has 'list' => (isa => 'Array', -default => (qw(foo bar baz)));
It also works for hashes:
has 'dict' => (isa => 'Hash', -default => (
'next' => 1,
'prev' => 2,
'data' => 'jumps over dog',
));
= OBJECT CONSTRUCTION
# TODO WRITE SOMETHING ABOUT OBJECT CONSTRUCTION HERE
# BUILD etc.
= OBJECT DESTRUCTION
# TODO WRITE SOMETHING ABOUT OBJECT DESTRUCTION HERE
# DEMOLISH etc.
= INHERITANCE
Inheritance is specified with the extends keyword
extends 'Foo'; # Single base class.
extends qw(Foo Bar Xuzzy); # Multiple inheritance.
=for implementation_status todo extends_append deadline => 2.0
By default {extends} overwrites the list of base classes,
but you can append classes if the first element is a plus sign ({+}):
extends + => qw(Fubar::Base);
you can also write
extends qw(+ Fubar::Base);
= COMPOSITION
An attribute type that has double colon ({::}) in it's name means composition.
has model => (isa => 'MyApp::Model');
Classes that ordinarily doesn't include double colon
can be described by tacking them at the end of the
class name.
has master => (isa => 'Catalyst::');
Can also say {composites} specifically
composites 'view' => 'MyApp::View';
The class will be automaticly created once you access the accessor.
So you can do this if the {MyApp::Model} class has a {select()} method:
my $self = MyClass->new();
$self->model->select('user', {user_id => 10});
Of course, this is just a default value, so if the user gives his/her own
instance it won't' be overwritten.
my $other_model = SomeOther::Model->new();
my $myclass = MyClass->new({ model => $other_model });
$myclass->select('user', {user-id => 10}); # <--- uses SomeOther::Model
=for implementation_status todo composite_build_option deadline => 2.0
You can do object intialization on the class level by using the build option
has model => (isa => 'MyApp::Model',
build => {
database_name => 'mydb',
});
If you want to do dynamic object initialization, you have to do that
in your {BUILD} method. Say you want to initialize the {model} object
differently if the user provides his own database name, you can do that like
this:
has model => (isa => 'MyApp::Model');
has database_name => (isa => 'String');
sub BUILD {
my ($self, $opts_ref) = @_;
# If the user provides a database name, and not a custom model
# instance
if (exists $opts_ref->{database_name) && !exists $opts_ref->{model}) {
# Create the model based on the database name
=for implementation_status todo dot::meta::get_attribute_info deadline => 2.0
# We get the class name from our metaclasss instead of just
# writing MyApp::Model->new, this lets us change the class name
# only once. Of course this might be too much of an abstraction
# for you, and you can just write MyApp::Model->new if you want,
# and it's probably just as good. But for the examples sake... :)
my $model_class = $self->dot::meta::get_attribute_info('model')->type;
my $model = $model_class->new({
database_name => $opts_ref->{database_name});
});
$self->set_model($model);
}
= METACLASSES
=for implementation_status design_phase metaclass deadline => 2.0
All Dot classes is associated with a metaclass. You can even set
the metaclass for a class with the q{-metaclass} class option.
You can get accesss to your metaclass instance like this:
my $metaclass = $self->meta;
The problem with this is that this only works with Dot classes because
Perl 5 does not have a standard metaclass interface (not even {meta} is
standard, but it is in Perl 6), so if you were trying to do something like
this
sub decorate_class_with_exception_attribute {
my ($self, $other_class) = @_;
my $metaclass = $other_class->metaclass;
$metaclass->define_attribute('exception', (isa => 'Object'));
$other_class->set_exception( Exceptions->new() );
return;
}
it would blow up in your face as soon as it hit a class not using Dot.
That's why we have the {dot::meta} pseudoclass. The above can be rewritten
using the {dot::meta::has} method to work perfectly for all (hash based)
classes like this:
use Carp 'confess';
sub decorate_class_with_exception_attribute {
my ($self, $other_class) = @_;
# test instance for incompatibility.
if (my $reason = $other_class->dot::meta::incompatible) {
confess "Cannot decorate: $reason";
}
$other_class->dot::meta::has('exception' => (isa => 'Object'));
$other_class->set_exception( Exception->new() );
return;
}
= UTILITY METHODS
== The {Maybe} keyword
If you don't care wether a method exists or not you can use the {Maybe} keyword:
my $return_value = Maybe { $self->close_map_file() }
{Maybe} will return undef if {close_map_file()} doesn't exist, or it's
return value if it does. It's something like the equivalent to:
my $return_value;
if ($self->has('close_map_file')) {
$return_value = $self->close_map_file()
}
=end wikidoc
# Local Variables:
# mode: cperl
# cperl-indent-level: 4
# fill-column: 78
# End:
# vim: expandtab tabstop=4 shiftwidth=4 shiftround