Introduction to Object Orientation
Dave Rolsky
What is OO For?
- Code reuse?
- Modelling "real world" objects?
- To build inheritance hierarchies?
None of the Above
- OO is not about reuse, modelling, or inheritance
The Value of OO
- Writing cleaner code through ...
- Separation of concerns
- Loose coupling
- Clearly defined APIs between components
OO is Just One Paradigm
- You probably already know imperative programming
- Functional programming - Lisp, Haskell, ML
- Declarative - SQL, Prolog
- Many others
Perl is Multi-Paradigm
- It is both possible and acceptable to mix paradigms
- Perl natively supports imperative, functional, and object-oriented programming
- CPAN offers many other paradigms
Why Moose?
- Native Perl OO is very verbose and fiddly
- Moose does the fiddling for you
- This presentation focuses on concepts
OO?
- An object is ...
- A single thing - like a file
- With attribute values - path, content, mime type
- And methods
An Object Has Methods
- Method is a subroutine that operates on that object
- For files -
write()
, move()
, read()
An Object Is ...
- Attributes + methods
- A well defined API
- A member of a class
Data + Operations
- Objects bundle data and operations on that data together
- Attributes are the data
- Methods are the operations
Class?
- Category of things
- Files, People, Webservers
- Defines attributes and/or methods
Perl Classes
- A
package
is a class
- Subroutines in the package are methods
- Perl has no native attribute syntax
- but Moose does
Perl Objects
# Never actually write this!
my $object = bless {}, 'MyClass';
use Scalar::Util 'blessed';
if ( blessed($object) { ... }
print blessed $object; # MyClass
Constructors
- The constructor makes a new object
- This is called instantiation
- An object is an instance of a class
- In Perl, the constructor is just another method
- Usually called new
A Very Simple Class
package File;
use Moose;
Slightly Less Simple Class
package File;
use Moose;
has path => ( is => 'ro' );
Making a File Object
use File;
my $file =
File->new( path => 'path/to/file' );
Method Invocation
- Methods are called with
->
- The arrow operator
- Calling a method is also called invocation
Defining Methods
- A method is a subroutine in a class's package
- Receives the invocant as the first argument
package File;
sub write {
my $self = shift;
# do something with file content
}
Method Invocation (again)
- We use
->
to invoke methods
my $file = File->new(
path => 'path/to/file',
content => $content,
);
$file->write();
Method Invocation
- Really just a fancy subroutine call
# Never write this!
File::write($file);
- Except for inheritance, so always use
->
Method Parameters
- Invocant is always first
- Other arguments are just like any Perl subroutine
package File;
sub rename {
my $self = shift;
my $new_name = shift;
# do something with new name
}
Passing Parameters
- Again, just like a regular subroutine
$file->rename('new-name.foo');
# Bad code again
File::rename( $file, 'new-name.foo' );
Method Resolution
- When you write
$file->write()
, how does Perl know what package write
is in?
- Package name is attached to the object (with
bless
)
# Don't copy this
my $object = bless {}, $class_name;
- Perl looks in that package for a subroutine named
write
- (and we'll cover inheritance and method resolution later)
Public Versus Private
- Public methods are documented for external use
- Private methods are not
Public/Private Conventions
- Perl does not enforce public/private distinctions
- All public methods should be documented in POD
- All private methods should start with an underscore -
sub _foo
- Private methods may be documented for use by subclasses
Object Construction
- Constructor methods return new objects
- In Perl, constructors are just methods
- Many other languages have a
new
keyword
- Can name the constructor anything and have multiple constructors
Writing a Constructor
- Moose writes your
new
method for you
package File;
use Moose;
# This now works
my $file = File->new();
An Alternate Constructor
sub open {
my ( $class, $path ) = @_;
open my $fh, '<', $path or die $!;
my $content = do { local $/; <$fh> };
return $class->new(
path => $path,
content => $content,
);
}
my $file = File->open( 'path/to/file' );
Attributes
- Properties of a specific object
- One file's name, content, mime type, etc.
- Perl has no built in attributes, but Moose does
Setting Attributes
- Can be done when constructing an object
- Or later, if the attribute is read-write
Attribute Example
package File;
use Moose;
has path => ( is => 'ro' );
has content => ( is => 'rw' );
Attribute Example
my $file =
File->new( path => 'path/to/file' );
$file->content($new_content);
Defining Attributes
- Attributes have many properties
- Read-only vs read-write
- Type (integer, string, DateTime object)
- Required-ness
Attribute Read-(Only|Write)
- Read-only can be set in the constructor
- Read-write can be changed later
- Read-only is a good default
Attribute Accessors
- Accessors get and set attribute values
- Provide encapsulation
- What, not how
Accessor Types
- Accessors can read and/or write attribute values
- Just reading is a getter or reader
- Just writing is a setter or writer
Accessor Naming
- Perl does not specify accessor names
-
Attribute name == method:
$object->attribute()
-
Separate read vs write:
$object->get_attribute()
$object->set_attribute()
- Lots of variations (private reader or writer, etc.)
Attribute Values
- An attribute can be anything
- String, number, array ref, another object
$person->birth_date()->year()
What's in a Class?
- A class does not require attributes
- A class does not require methods
- A class with neither is valid, but probably useless
What's in a Class?
- Most classes will have at least one attribute
- Some classes will not have any methods besides accessors
- Many classes will not have any public methods besides accessors
Minimal Class Example
package File;
use Moose;
use autodie;
has path => ( is => 'ro' );
has content => ( is => 'rw' );
sub write {
my $self = shift;
open my $fh, '>', $self->path();
print {$fh} $self->content();
}
Intermission
Inheritance, Roles, Polymorphism
- What makes this different from imperative programming?
write_file( $path, $content )
- Inheritance, Roles, and Polymorphism are some of the big differences
Inheritance
- One class can inherit from another class
- Also called subclassing
- We say superclass and subclass
- Or parent and child classes
Why Subclass?
- Subclassing (inheritance) is for specialization
- Adding or modifying the parent class's behavior
- Subclassing is is not a mechanism for code reuse
Inheritance Defined
- The child class has all of the methods and attributes of its parent class(es)
- It can add methods or attributes
- It can change an inherited attribute's properties
- It can change a method's behavior
Inheritance Example
File::MP3
will inherit from File
- Adds a
$file->play()
method
Inheritance in Code
package File::MP3;
use Moose;
extends 'File';
Overriding Methods
- Overriding a method - subclass defines a method of the same name as the superclass
- The subclass method can call the parent's (if it wants to)
Overriding Example
package File::MP3;
use Moose;
extends 'File';
has title => ( is => 'ro' );
override write => sub {
my $self = shift;
# make sure content contains mp3 tags
super();
};
Inheritance Continued
- It is possible to have subclasses of subclasses
- A subclass can have multiple parents - called multiple inheritance
- Multiple inheritance is almost always a bad idea
Method Resolution
- When you write
$object->method()
, how does Perl find this method?
- First looks in the class for
$object
- Next looks that class's parent, grandparent, and so on
- This is the method resolution order
- Multiple inheritance is more complicated
Polymorphism
- Polymorphism is a fancy way of saying that multiple classes share an API
- Both
File
and File::MP3
have write()
methods
- Can apply to superclass and subclass, or just any two classes
- Both
Graph
and Image
can render()
Polymorphism Versus If/Then
if ( $file->{type} eq 'mp3' ) {
# write file with title tag
} else {
# write file
}
# Polymorphism lets us write ...
$file->write();
Why Polymorphism Matters
- Let's you define your APIs by what they do
- Let's you not worry about how something is implemented
- For example, a
DisplayManager
can just require an object that implements a render()
method
- It does not care what is being rendered
Encapsulation
- Objects should document what they do, not how they do it
- An object's internals should be opaque to its callers
- This is encapsulation
Why Encapsulation Matters
- Lets you change the internal implementation without breaking the API
- Encourages loose coupling between components
- Makes polymorphism possible
Encapsulation How-To
- Never treat the object like a Perl reference
- This applies even inside the class
- Always use accessor methods to get and set attributes
- Moose makes this easy
Overloading
- Overloading lets objects respond to native Perl operators
+
, -
, ""
(stringification), etc.
- Be careful with this!
- It's easy to make insane APIs with this
- But overloading a few operators can be useful
Overloading Example
package File;
use Moose;
use overload q{""} => 'as_string';
sub as_string {
my $self = shift;
return 'File: ' . $self->path();
}
print File->new( path => 'foo/bar' );
# File: foo/bar
Roles
- Roles are another way to organize OO code
- Roles can be used by many classes
- A role is something a class does, not something a class is
- Roles are a mechanism for code reuse
Roles and Classes
- A class can consume many roles
- Roles are composed into a class
- Roles can consume other roles
Roles Versus Classes
- A role does not have a constructor
- A role cannot be instantiated
- A role must be consumed by a class to be used
What's in a Role?
- A role can include attributes and methods
- It can also require that the consuming class implement certain methods
- Taken all together, this defines an interface
Simple Role Example
package Comparable;
use Moose::Role;
requires 'compare';
Another Role Example
package HasLogger;
use Moose::Role;
has logger => ( is => 'ro' );
sub log {
my $self = shift;
$self->logger()->log(@_);
}
Consuming Roles
package File;
use Moose;
with 'HasLogger';
Consuming Multiple Roles
package File;
use Moose;
with 'Comparable', 'HasLogger';
sub compare { ... }
Roles Versus Inheritance
- Inheritance is for specialization
- Roles are for capabilities
- Roles are for code reuse
- Roles let you avoid multiple inheritance
Roles and Polymorphism
- Polymorphism is about different classes having the same API
- Roles makes this easy
- Any class that consumes the
Comparable
role has a compare()
method
Role Introspection
if ( $object->does('Comparable')
&& $other->does('Comparable') ) {
if ( $object->compare($other) ) {
print "They're the same\n";
}
else {
print "They're not the same\n";
}
}
else {
print "They're not comparable\n";
}
Composition
- Objects can contain other objects
- A
File
object may return its last modified time as a DateTime
- This is called composition
Composition Example
package File;
use Moose;
has last_mod => (
is => 'ro',
isa => 'DateTime',
);
print $file->last_mod()->ymd();
Composition
- Composition is very common
- Lots of composition can make complicated APIs
- "This method returns a Foo object, which contains a Bar and a Baz object"
Delegation
- Delegation is a way to hide composition
- Delegating object passes method calls on to delegatee object
- Moose builds this in to attribute declaration
Delegation Example
package File;
use Moose;
has last_mod => (
is => 'ro',
isa => 'DateTime',
handles => { last_mod_ymd => 'ymd' },
);
print $file->last_mod_ymd();
Delegation Is Good
- It reduces the number of APIs someone needs to use
- Lets you rearrange internals and preserve the public API
- Fewer objects for users of your code to learn about
Advice
- Keep public APIs small
- Default to read-only attributes
- Avoid multiple inheritance
- Avoid inheritance - use roles instead
When to Use OO
- As published in Damian Conway's Perl Best Practices
(copyright 2004, Published by O'Reilly Media, Inc.)
- The system being designed is large, or is likely to become large.
- The data can be aggregated into obvious structures, especially if
there's a large amount of data in each aggregate.
When to Use OO
-
The various types of data aggregate form a natural hierarchy that
facilitates the use of inheritance and polymorphism.
-
Dave's Addendum: ... or the various types of data aggregate share
common behaviors that faciliate the use of roles.
- You have a piece of data on which many different operations are applied.
When to Use OO
- You need to perform the same general operations on related types of
data, but with slight variations depending on the specific type of data
the operations are applied to.
- It's likely you'll have to add new data types later.
- The typical interactions between pieces of data are best represented by
operators.
When to Use OO
- The implementation of individual components of the system is likely to
change over time.
- The system design is already object-oriented.
- Large numbers of other programmers will be using your code modules.
Live (Clothed) Coding
Questions?
The End