Skip to content

Commit

Permalink
Edited Chapter 7 for 2014 edition.
Browse files Browse the repository at this point in the history
  • Loading branch information
chromatic committed Dec 22, 2013
1 parent 1fa721a commit 4983351
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 228 deletions.
58 changes: 32 additions & 26 deletions sections/advanced_oo.pod
Expand Up @@ -2,10 +2,10 @@

Z<advanced_oo>

Creating and using objects in Perl 5 with Moose (L<moose>) is easy.
I<Designing> good programs is not. You must balance between designing too
little and too much. Only practical experience can help you understand the
most important design techniques, but several principles can guide you.
Creating and using objects in Perl with Moose (L<moose>) is easy. I<Designing>
good programs is not. You must balance between designing too little and too
much. Only practical experience can help you understand the most important
design techniques, but several principles can guide you.

=head2 Favor Composition Over Inheritance

Expand All @@ -21,14 +21,15 @@ with code declared elsewhere?
X<OO; is-a>
X<OO; has-a>

Inheritance is but one of many tools. A C<Car> may extend C<Vehicle::Wheeled>
Inheritance is only one of many tools for OO programmers. It's not always the
right tool; it's often the wrong tool. A C<Car> may extend C<Vehicle::Wheeled>
(an I<is-a relationship>), but C<Car> may better I<contain> several C<Wheel>
objects as instance attributes (a I<has-a relationship>).

Decomposing complex classes into smaller, focused entities (whether classes or
roles) improves encapsulation and reduces the possibility that any one class or
role will grow to do too much. Smaller, simpler, and better encapsulated
entities are easier to understand, test, and maintain.
role does too much. Smaller, simpler, and better encapsulated entities are
easier to understand, test, and maintain.

=head2 Single Responsibility Principle

Expand All @@ -41,7 +42,8 @@ C<Job> object may represent business responsibilities. Separating these
entities in terms of their responsibilities allows the C<Employee> class to
consider only the problem of managing information specific to who the person is
and the C<Job> class to represent what the person does. (Two C<Employee>s may
have a C<Job>-sharing arrangement, for example.)
have a C<Job>-sharing arrangement, for example, or one C<Employee> may have the
CFO and the COO C<Job>s.)

When each class has a single responsibility, you improve the encapsulation of
class-specific data and behaviors and reduce coupling between classes.
Expand All @@ -50,26 +52,26 @@ class-specific data and behaviors and reduce coupling between classes.

X<DRY>

Complexity and duplication complicate development and maintenance. The DRY
Complexity and duplication complicate development and maintenance. The I<DRY>
principle (Don't Repeat Yourself) is a reminder to seek out and to eliminate
duplication within the system. Duplication exists in data as well as in code.
Instead of repeating configuration information, user data, and other artifacts
within your system, create a single, canonical representation of that
information from which you can generate the other artifacts.

This principle helps to reduce the possibility that important parts of your
system can get unsynchronized, and helps you to find the optimal representation
of the system and its data.
system will get unsynchronized. It also helps you to find the optimal
representation of the system and its data.

=head2 Liskov Substitution Principle

X<OO; Liskov Substitution Principle>

The Liskov substitution principle suggests that you should be able to
substitute a specialization of a class or a role for the original without
violating the API of the original. In other words, an object should be as or
violating the API of the original. In other words, an object should be as or
more general with regard to what it expects and at least as specific about what
it produces.
it produces as the object it replaces.

Imagine two classes, C<Dessert> and its child class C<PecanPie>. If the
classes follow the Liskov substitution principle, you can replace every use of
Expand All @@ -95,7 +97,10 @@ mechanisms by which to coerce data of one type to data of another type.
X<C<Moose::Util::TypeConstraints>>
X<C<MooseX::Types>>

See C<Moose::Util::TypeConstraints> and C<MooseX::Types> for more information.
For example, you may wish to allow people to provide dates to a C<Ledger> entry
as strings while representing them as C<DateTime> instances internally. You can
do this by creating a Date type and adding a coercion from string types. See
C<Moose::Util::TypeConstraints> and C<MooseX::Types> for more information.

=head2 Immutability

Expand All @@ -108,18 +113,19 @@ methods to get and set internal values. This simple technique leads to the
unfortunate temptation to spread the object's responsibilities throughout the
entire system.

With a well-designed object, you tell it what to do and not how to do it. As a
rule of thumb, if you find yourself accessing object instance data (even
through accessor methods), you may have too much access to an object's
internals.
With a well-designed object, you tell it I<what to do>, not I<how to do it>. As
a rule of thumb, if you find yourself accessing object instance data (even
through accessor methods) outside of the object itself, you may have too much
access to an object's internals.

One approach to preventing this behavior is to consider objects as immutable.
You can prevent this inappropriate access by making your objects immutable.
Provide the necessary data to their constructors, then disallow any
modifications of this information from outside the class. Expose no methods to
mutate instance data. The objects so constructed are always valid after their
construction and cannot become invalid through external manipulation. This
takes tremendous discipline to achieve, but the resulting systems are robust,
testable, and maintainable.

Some designs go as far as to prohibit the modification of instance data
I<within> the class itself, though this is much more difficult to achieve.
mutate instance data. Once you've constructed such an object, you know it's
always in a valid state. You can never modify its data to put it in an invalid
state.

This takes tremendous discipline, but the resulting systems are robust,
testable, and maintainable. Some designs go as far as to prohibit the
modification of instance data I<within> the class itself, though this is much
more difficult to achieve.
93 changes: 46 additions & 47 deletions sections/blessed_references.pod
Expand Up @@ -14,20 +14,19 @@ Perl 5's core object system is deliberately minimal. It has only three rules:

=back

You can build anything else out of those three rules, but that's all you get by
default. This minimalism can be impractical for larger projects--in particular,
the possibilities for greater abstraction through metaprogramming
(L<code_generation>) are awkward and limited. Moose (L<moose>) is a better
choice for modern programs larger than a couple of hundred lines, although
plenty of legacy code still uses Perl 5's default OO.
You can build anything else out of those three rules. This minimalism can be
impractical for larger projects--in particular, the possibilities for greater
abstraction through metaprogramming (L<code_generation>) are awkward and
limited. Moose (L<moose>) is a better choice for modern programs larger than a
couple of hundred lines, although lots of legacy code still uses Perl's default
OO.

X<OO; C<bless>>
X<builtins; C<bless>>

The final piece of Perl 5 core OO is the blessed reference. The C<bless>
builtin associates the name of a class with a reference. That reference is now
a valid invocant, and Perl will perform method dispatch on it, using the
associated class.
You've seen the first two rules already. The C<bless> builtin associates the
name of a class with a reference. That reference is now a valid invocant, and
Perl will perform method dispatch on it, using the associated class.

X<OO; constructors>
X<OO; class methods>
Expand All @@ -38,10 +37,11 @@ constructors have the name C<new()>, but this is not a requirement.
Constructors are also almost always I<class methods>.

C<bless> takes two arguments, a reference and a class name. It evaluates to the
reference. The reference may be empty. The class does not have to exist yet.
You may even use C<bless> outside of a constructor or a class (though all but
the simplest programs should use real constructors). The canonical constructor
resembles:
reference. The reference may be to an empty hash or array or a scalar
containing an undefined value. The class does not have to exist yet. You may
even use C<bless> outside of a constructor or a class (though all but the
simplest programs should use real constructors). A constructor can be as simple
as:

=begin programlisting

Expand All @@ -54,8 +54,8 @@ resembles:
=end programlisting

By design, this constructor receives the class name as the method's invocant.
You may also hard-code the name of a class, at the expense of flexibility.
Parametric constructor allows reuse through inheritance, delegation, or
You may also hard-code the name of a class, at the expense of flexibility. A
parametric constructor allows reuse through inheritance, delegation, or
exporting.

X<OO; instance data>
Expand All @@ -66,14 +66,14 @@ references are most common, but you can bless any type of reference:

=begin programlisting

my $array_obj = bless [], $class;
my $scalar_obj = bless \$scalar, $class;
my $sub_obj = bless \&some_sub, $class;
my $array_obj = bless [], $class;
my $scalar_obj = bless \$scalar, $class;
my $func_obj = bless \&some_func, $class;

=end programlisting

Moose classes define object attributes declaratively, but Perl 5's default OO
is lax. A class representing basketball players which stores jersey number and
Moose classes define object attributes declaratively, but Perl's default OO is
lax. A class representing basketball players which stores jersey number and
position might use a constructor like:

=begin programlisting
Expand All @@ -93,11 +93,11 @@ position might use a constructor like:

=begin programlisting

my $joel = Player->new( number => 10,
my $joel = Player->new( number => 10,
position => 'center' );

my $dante = Player->new( number => 33,
position => 'forward' );
my $damian = Player->new( number => 0,
position => 'guard' );

=end programlisting

Expand All @@ -124,7 +124,7 @@ representation may break other code. Accessor methods are safer:

=end programlisting

... and now you're starting to write manually what Moose gives you for free.
... and now you're starting to write yourself what Moose gives you for free.
Better yet, Moose encourages people to use accessors instead of direct access
by hiding the accessor generation code. Goodbye, temptation.

Expand All @@ -137,17 +137,17 @@ Given a blessed reference, a method call of the form:

=begin programlisting

my $number = $joel->number();
my $number = $joel->number;

=end programlisting

... looks up the name of the class associated with the blessed reference
C<$joel>--in this case, C<Player>. Next, Perl looks for a functionN<Remember
that Perl 5 makes no distinction between functions in a namespace and methods.>
named C<number()> in C<Player>. If no such function exists and if C<Player>
extends class, Perl looks in the parent class (and so on and so on) until it
finds a C<number()>. If Perl finds C<number()>, it calls that method with
C<$joel> as an invocant.
extends a parent class, Perl looks in the parent class (and so on and so on)
until it finds a C<number()>. If Perl finds C<number()>, it calls that method
with C<$joel> as an invocant.

=begin tip Keeping Namespaces Clean

Expand All @@ -160,7 +160,7 @@ The C<namespace::autoclean> CPAN module can help avoid unintentional collisions
X<C<@ISA>>
X<OO; C<@ISA>>

Moose provides C<extends> to track inheritance relationships, but Perl 5 uses a
Moose provides C<extends> to track inheritance relationships, but Perl uses a
package global variable named C<@ISA>. The method dispatcher looks in each
class's C<@ISA> to find the names of its parent classes. If C<InjuredPlayer>
extends C<Player>, you might write:
Expand Down Expand Up @@ -189,8 +189,8 @@ pragma, but C<parent> superseded C<base> in Perl 5.10.>:

=end programlisting

Moose has its own metamodel which stores extended inheritance information; this
offers additional features.
Moose has its own metamodel which stores extended inheritance information. This
allows Moose to provide additional metaprogramming opportunities.

X<multiple inheritance>
X<OO; inheritance>
Expand All @@ -216,26 +216,26 @@ X<OO; C<AUTOLOAD>>
X<methods; C<AUTOLOAD>>

If there is no applicable method in the invocant's class or any of its
superclasses, Perl 5 will next look for an C<AUTOLOAD()> function (L<autoload>)
superclasses, Perl will next look for an C<AUTOLOAD()> function (L<autoload>)
in every class according to the selected method resolution order. Perl will
invoke any C<AUTOLOAD()> it finds to provide or decline the desired method.

C<AUTOLOAD()> makes multiple inheritance much more difficult to understand.

=head2 Method Overriding and SUPER

As with Moose, you may override methods in the core Perl 5 OO. Unlike Moose,
core Perl 5 provides no mechanism for indicating your I<intent> to override a
parent's method. Worse yet, any function you predeclare, declare, or import
into the child class may override a method in the parent class by having the
same name. Even if you forget to use the C<override> system of Moose, at least
it exists. Core Perl 5 OO offers no such protection.
As with Moose, you may override methods in basic Perl OO. Unlike Moose, Perl
provides no mechanism for indicating your I<intent> to override a parent's
method. Worse yet, any function you predeclare, declare, or import into the
child class may override a method in the parent class by having the same name.
Even if you forget to use the C<override> system of Moose, at least it exists.
Basic Perl OO offers no such protection.

X<builtins; C<SUPER::>>

To override a method in a child class, declare a method of the same name as the
method in the parent. Within an overridden method, call the parent method with
the C<SUPER::> dispatch hint:
To override a method in a child class, declare a method with the same name as
the method in the parent. Within an overridden method, call the parent method
with the C<SUPER::> dispatch hint:

=begin programlisting

Expand Down Expand Up @@ -268,8 +268,8 @@ C<super()> does not suffer the same problem.

=head2 Strategies for Coping with Blessed References

If blessed references seem minimal and tricky and confusing, they are. Moose is
a tremendous improvement. Use it whenever possible. If you do find yourself
Blessed references may seem minimal and tricky and confusing. They are. Moose
is much easier to use, so use it whenever possible. If you do find yourself
maintaining code which uses blessed references, or if you can't convince your
team to use Moose in full yet, you can work around some of the problems of
blessed references with discipline.
Expand All @@ -278,9 +278,8 @@ X<C<Class::Accessor>>

=over 4

=item * Use accessor methods pervasively, even within methods in your class.
Consider using a module such as C<Class::Accessor> to avoid repetitive
boilerplate.
=item * Use accessor methods pervasively, even within methods in your class. A
module such as C<Class::Accessor> helps to avoid repetitive boilerplate.

=item * Avoid C<AUTOLOAD()> where possible. If you I<must> use it, use forward
declarations of your functions (L<functions>) to help Perl know which
Expand Down

0 comments on commit 4983351

Please sign in to comment.