1# PODNAME: Moose::Cookbook::Basics::Point_AttributesAndSubclassing 2# ABSTRACT: Point and Point3D classes, showing basic attributes and subclassing. 3 4__END__ 5 6=pod 7 8=encoding UTF-8 9 10=head1 NAME 11 12Moose::Cookbook::Basics::Point_AttributesAndSubclassing - Point and Point3D classes, showing basic attributes and subclassing. 13 14=head1 VERSION 15 16version 2.2201 17 18=head1 SYNOPSIS 19 20 package Point; 21 use Moose; 22 23 has 'x' => (isa => 'Int', is => 'rw', required => 1); 24 has 'y' => (isa => 'Int', is => 'rw', required => 1); 25 26 sub clear { 27 my $self = shift; 28 $self->x(0); 29 $self->y(0); 30 } 31 32 package Point3D; 33 use Moose; 34 35 extends 'Point'; 36 37 has 'z' => (isa => 'Int', is => 'rw', required => 1); 38 39 after 'clear' => sub { 40 my $self = shift; 41 $self->z(0); 42 }; 43 44 package main; 45 46 # hash or hashrefs are ok for the constructor 47 my $point1 = Point->new(x => 5, y => 7); 48 my $point2 = Point->new({x => 5, y => 7}); 49 50 my $point3d = Point3D->new(x => 5, y => 42, z => -5); 51 52=head1 DESCRIPTION 53 54This is the classic Point example. It is taken directly from the Perl 556 Apocalypse 12 document, and is similar to the example found in the 56classic K&R C book as well. 57 58As with all Perl 5 classes, a Moose class is defined in a package. 59Moose handles turning on C<strict> and C<warnings> for us, so all we 60need to do is say C<use Moose>, and no kittens will die. 61 62When Moose is loaded, it exports a set of sugar functions into our 63package. This means that we import some functions which serve as Moose 64"keywords". These aren't real language keywords, they're just Perl 65functions exported into our package. 66 67Moose automatically makes our package a subclass of L<Moose::Object>. 68The L<Moose::Object> class provides us with a constructor that 69respects our attributes, as well other features. See L<Moose::Object> 70for details. 71 72Now, onto the keywords. The first one we see here is C<has>, which 73defines an instance attribute in our class: 74 75 has 'x' => (isa => 'Int', is => 'rw', required => 1); 76 77This will create an attribute named C<x>. The C<isa> parameter says 78that we expect the value stored in this attribute to pass the type 79constraint for C<Int> (1). The accessor generated for this attribute 80will be read-write. 81 82The C<< required => 1 >> parameter means that this attribute must be 83provided when a new object is created. A point object without 84coordinates doesn't make much sense, so we don't allow it. 85 86We have defined our attributes; next we define our methods. In Moose, 87as with regular Perl 5 OO, a method is just a subroutine defined 88within the package: 89 90 sub clear { 91 my $self = shift; 92 $self->x(0); 93 $self->y(0); 94 } 95 96That concludes the B<Point> class. 97 98Next we have a subclass of B<Point>, B<Point3D>. To declare our 99superclass, we use the Moose keyword C<extends>: 100 101 extends 'Point'; 102 103The C<extends> keyword works much like C<use base>/C<use parent>. First, 104it will attempt to load your class if needed. However, unlike C<base>, the 105C<extends> keyword will I<overwrite> any previous values in your 106package's C<@ISA>, where C<use base> will C<push> values onto the 107package's C<@ISA>. 108 109It is my opinion that the behavior of C<extends> is more intuitive. 110(2). 111 112Next we create a new attribute for B<Point3D> called C<z>. 113 114 has 'z' => (isa => 'Int', is => 'rw', required => 1); 115 116This attribute is just like B<Point>'s C<x> and C<y> attributes. 117 118The C<after> keyword demonstrates a Moose feature called "method 119modifiers" (or "advice" for the AOP inclined): 120 121 after 'clear' => sub { 122 my $self = shift; 123 $self->z(0); 124 }; 125 126When C<clear> is called on a B<Point3D> object, our modifier method 127gets called as well. Unsurprisingly, the modifier is called I<after> 128the real method. 129 130In this case, the real C<clear> method is inherited from B<Point>. Our 131modifier method receives the same arguments as those passed to the 132modified method (just C<$self> here). 133 134Of course, using the C<after> modifier is not the only way to 135accomplish this. This B<is> Perl, right? You can get the same results 136with this code: 137 138 sub clear { 139 my $self = shift; 140 $self->SUPER::clear(); 141 $self->z(0); 142 } 143 144You could also use another Moose method modifier, C<override>: 145 146 override 'clear' => sub { 147 my $self = shift; 148 super(); 149 $self->z(0); 150 }; 151 152The C<override> modifier allows you to use the C<super> keyword to 153dispatch to the superclass's method in a very Ruby-ish style. 154 155The choice of whether to use a method modifier, and which one to use, 156is often a question of style as much as functionality. 157 158Since B<Point> inherits from L<Moose::Object>, it will also inherit 159the default L<Moose::Object> constructor: 160 161 my $point1 = Point->new(x => 5, y => 7); 162 my $point2 = Point->new({x => 5, y => 7}); 163 164 my $point3d = Point3D->new(x => 5, y => 42, z => -5); 165 166The C<new> constructor accepts a named argument pair for each 167attribute defined by the class, which you can provide as a hash or 168hash reference. In this particular example, the attributes are 169required, and calling C<new> without them will throw an error. 170 171 my $point = Point->new( x => 5 ); # no y, kaboom! 172 173From here on, we can use C<$point> and C<$point3d> just as you would 174any other Perl 5 object. For a more detailed example of what can be 175done, you can refer to the 176F<t/recipes/basics_point_attributesandsubclassing.t> test file. 177 178=head2 Moose Objects are Just Hashrefs 179 180While this all may appear rather magical, it's important to realize 181that Moose objects are just hash references under the hood (3). For 182example, you could pass C<$self> to C<Data::Dumper> and you'd get 183exactly what you'd expect. 184 185You could even poke around inside the object's data structure, but 186that is strongly discouraged. 187 188The fact that Moose objects are hashrefs means it is easy to use Moose 189to extend non-Moose classes, as long as they too are hash 190references. If you want to extend a non-hashref class, check out 191C<MooseX::InsideOut>. 192 193=head1 CONCLUSION 194 195This recipe demonstrates some basic Moose concepts, attributes, 196subclassing, and a simple method modifier. 197 198=head1 FOOTNOTES 199 200=over 4 201 202=item (1) 203 204Moose provides a number of builtin type constraints, of which C<Int> 205is one. For more information on the type constraint system, see 206L<Moose::Util::TypeConstraints>. 207 208=item (2) 209 210The C<extends> keyword supports multiple inheritance. Simply pass all 211of your superclasses to C<extends> as a list: 212 213 extends 'Foo', 'Bar', 'Baz'; 214 215=item (3) 216 217Moose supports using instance structures other than blessed hash 218references (such as glob references - see L<MooseX::GlobRef>). 219 220=back 221 222=head1 SEE ALSO 223 224=over 4 225 226=item Method Modifiers 227 228The concept of method modifiers is directly ripped off from CLOS. A 229great explanation of them can be found by following this link. 230 231L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> 232 233=back 234 235=begin testing 236 237my $point = Point->new( x => 1, y => 2 ); 238isa_ok( $point, 'Point' ); 239isa_ok( $point, 'Moose::Object' ); 240 241is( $point->x, 1, '... got the right value for x' ); 242is( $point->y, 2, '... got the right value for y' ); 243 244$point->y(10); 245is( $point->y, 10, '... got the right (changed) value for y' ); 246 247isnt( 248 exception { 249 $point->y('Foo'); 250 }, 251 undef, 252 '... cannot assign a non-Int to y' 253); 254 255isnt( 256 exception { 257 Point->new(); 258 }, 259 undef, 260 '... must provide required attributes to new' 261); 262 263$point->clear(); 264 265is( $point->x, 0, '... got the right (cleared) value for x' ); 266is( $point->y, 0, '... got the right (cleared) value for y' ); 267 268# check the type constraints on the constructor 269 270is( 271 exception { 272 Point->new( x => 0, y => 0 ); 273 }, 274 undef, 275 '... can assign a 0 to x and y' 276); 277 278isnt( 279 exception { 280 Point->new( x => 10, y => 'Foo' ); 281 }, 282 undef, 283 '... cannot assign a non-Int to y' 284); 285 286isnt( 287 exception { 288 Point->new( x => 'Foo', y => 10 ); 289 }, 290 undef, 291 '... cannot assign a non-Int to x' 292); 293 294# Point3D 295 296my $point3d = Point3D->new( { x => 10, y => 15, z => 3 } ); 297isa_ok( $point3d, 'Point3D' ); 298isa_ok( $point3d, 'Point' ); 299isa_ok( $point3d, 'Moose::Object' ); 300 301is( $point3d->x, 10, '... got the right value for x' ); 302is( $point3d->y, 15, '... got the right value for y' ); 303is( $point3d->{'z'}, 3, '... got the right value for z' ); 304 305$point3d->clear(); 306 307is( $point3d->x, 0, '... got the right (cleared) value for x' ); 308is( $point3d->y, 0, '... got the right (cleared) value for y' ); 309is( $point3d->z, 0, '... got the right (cleared) value for z' ); 310 311isnt( 312 exception { 313 Point3D->new( x => 10, y => 'Foo', z => 3 ); 314 }, 315 undef, 316 '... cannot assign a non-Int to y' 317); 318 319isnt( 320 exception { 321 Point3D->new( x => 'Foo', y => 10, z => 3 ); 322 }, 323 undef, 324 '... cannot assign a non-Int to x' 325); 326 327isnt( 328 exception { 329 Point3D->new( x => 0, y => 10, z => 'Bar' ); 330 }, 331 undef, 332 '... cannot assign a non-Int to z' 333); 334 335isnt( 336 exception { 337 Point3D->new( x => 10, y => 3 ); 338 }, 339 undef, 340 '... z is a required attribute for Point3D' 341); 342 343# test some class introspection 344 345can_ok( 'Point', 'meta' ); 346isa_ok( Point->meta, 'Moose::Meta::Class' ); 347 348can_ok( 'Point3D', 'meta' ); 349isa_ok( Point3D->meta, 'Moose::Meta::Class' ); 350 351isnt( 352 Point->meta, Point3D->meta, 353 '... they are different metaclasses as well' 354); 355 356# poke at Point 357 358is_deeply( 359 [ Point->meta->superclasses ], 360 ['Moose::Object'], 361 '... Point got the automagic base class' 362); 363 364my @Point_methods = qw(meta x y clear); 365my @Point_attrs = ( 'x', 'y' ); 366 367is_deeply( 368 [ sort @Point_methods ], 369 [ sort Point->meta->get_method_list() ], 370 '... we match the method list for Point' 371); 372 373is_deeply( 374 [ sort @Point_attrs ], 375 [ sort Point->meta->get_attribute_list() ], 376 '... we match the attribute list for Point' 377); 378 379foreach my $method (@Point_methods) { 380 ok( Point->meta->has_method($method), 381 '... Point has the method "' . $method . '"' ); 382} 383 384foreach my $attr_name (@Point_attrs) { 385 ok( Point->meta->has_attribute($attr_name), 386 '... Point has the attribute "' . $attr_name . '"' ); 387 my $attr = Point->meta->get_attribute($attr_name); 388 ok( $attr->has_type_constraint, 389 '... Attribute ' . $attr_name . ' has a type constraint' ); 390 isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); 391 is( $attr->type_constraint->name, 'Int', 392 '... Attribute ' . $attr_name . ' has an Int type constraint' ); 393} 394 395# poke at Point3D 396 397is_deeply( 398 [ Point3D->meta->superclasses ], 399 ['Point'], 400 '... Point3D gets the parent given to it' 401); 402 403my @Point3D_methods = qw( meta z clear ); 404my @Point3D_attrs = ('z'); 405 406is_deeply( 407 [ sort @Point3D_methods ], 408 [ sort Point3D->meta->get_method_list() ], 409 '... we match the method list for Point3D' 410); 411 412is_deeply( 413 [ sort @Point3D_attrs ], 414 [ sort Point3D->meta->get_attribute_list() ], 415 '... we match the attribute list for Point3D' 416); 417 418foreach my $method (@Point3D_methods) { 419 ok( Point3D->meta->has_method($method), 420 '... Point3D has the method "' . $method . '"' ); 421} 422 423foreach my $attr_name (@Point3D_attrs) { 424 ok( Point3D->meta->has_attribute($attr_name), 425 '... Point3D has the attribute "' . $attr_name . '"' ); 426 my $attr = Point3D->meta->get_attribute($attr_name); 427 ok( $attr->has_type_constraint, 428 '... Attribute ' . $attr_name . ' has a type constraint' ); 429 isa_ok( $attr->type_constraint, 'Moose::Meta::TypeConstraint' ); 430 is( $attr->type_constraint->name, 'Int', 431 '... Attribute ' . $attr_name . ' has an Int type constraint' ); 432} 433 434=end testing 435 436=head1 AUTHORS 437 438=over 4 439 440=item * 441 442Stevan Little <stevan@cpan.org> 443 444=item * 445 446Dave Rolsky <autarch@urth.org> 447 448=item * 449 450Jesse Luehrs <doy@cpan.org> 451 452=item * 453 454Shawn M Moore <sartak@cpan.org> 455 456=item * 457 458יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org> 459 460=item * 461 462Karen Etheridge <ether@cpan.org> 463 464=item * 465 466Florian Ragwitz <rafl@debian.org> 467 468=item * 469 470Hans Dieter Pearcey <hdp@cpan.org> 471 472=item * 473 474Chris Prather <chris@prather.org> 475 476=item * 477 478Matt S Trout <mstrout@cpan.org> 479 480=back 481 482=head1 COPYRIGHT AND LICENSE 483 484This software is copyright (c) 2006 by Infinity Interactive, Inc. 485 486This is free software; you can redistribute it and/or modify it under 487the same terms as the Perl 5 programming language system itself. 488 489=cut 490