3. What Are Objects?
Actually, "What are objects?" is a silly question because you already know
what an object is. Trust your instincts. The book you are reading is an object.
The knife and fork you eat with are objects. In short, your life is filled with
them.
The question that really needs to be asked is, "What are classes?" You see,
all object-oriented techniques use classes to do the real work. A class
is a combination of variables and functions designed to emulate an object.
However, when referring to variables in a class, object-oriented folks use the
term properties; and when referring to functions in a class, the term
method is used.
I'm not sure why new terminology was developed for object-oriented
programming. Because the terms are now commonplace in the object-oriented
documentation and products, you need to learn and become comfortable with them
in order to work efficiently.
In this chapter, you see how to represent objects in Perl using classes,
methods, and properties. In addition, you look at the definitions of some big
words like abstraction, encapsulation, inheritance, and
polymorphism.
Following are short definitions for these words. The sections that follow
expand on these definitions and show some examples of their use.
- Abstraction: Information about an object (its properties) can be
accessed in a manner that isolates how data is stored from how it is accessed
and used.
- Encapsulation: The information about an object and functions that
manipulate the information (its methods) are stored together.
- Inheritance: Classes can inherit properties and methods from one or
more parent classes.
- Polymorphism: A child class can redefine a method already defined
in the parent class.
3.1. Learning about Classes
Before looking at specific examples of object-oriented Perl code, you need to see some
generic examples. Looking at generic examples while learning the “standard”
object-oriented terminology will ensure that you have a firm grasp of the
concepts. If you had to learn new Perl concepts at the same time as the object
concepts, something might be lost because of information overload.
Classes are used to group and describe object types. Remember the
character classes from [](./regular-expressions.md). A class in the
object-oriented world is essentially the same thing. Let's create some classes
for an inventory system for a pen and pencil vendor. Start with a pen object.
How could you describe a pen from an inventory point of view?
Well, the pen probably has a part number, and you need to know how many of
them there are. The color of the pen might also be important. What about the
level of ink in the cartridge - is that important? Probably not to an inventory
system because all the pens will be new and therefore full.
The thought process embodied in the previous paragraph is called
modeling. Modeling is the process of deciding what will go into your
objects. In essence, you create a model of the world out of objects.
Tip |
The terms object and class are pretty
interchangeable. Except that a class might be considered an object
described in computer language, whereas an object is just an
object. |
Objects are somewhat situationally dependent. The description of an object,
and the class, depend on what needs to be done. If you were attempting to design
a school course scheduling program, your objects would be very different than if
you were designing a statistics program.
Now back to the inventory system. You were reading about pens and how they
had colors and other identifying features. In object talk, these features are
called properties. Figure 14.1 shows how the pen class looks at this
stage of the discussion.
Fig. 14.1 - The Pen class and its properties.
Now that you have a class, it's time to generalize. Some people generalize
first. I like to look at the details first and then extract the common
information. Of course, usually you'd need several classes before any common
features will appear. But because I've already thought this example through, you
can cheat a little.
It's pretty obvious that all inventory items will need a part number and that
each will have its own quantity-on-hand value. Therefore, you can create a more
general class than Pen. Let's call it Inventory_item. Figure
14.2 shows this new class.
Fig. 14.2 - The Inventory_item class and its properties.
Because some of Pen's properties are now also in
Inventory_item, you need some mechanism or technique to avoid
repetition of information. This is done by deriving the Pen class from
Inventory_item. In other words, Inventory_item becomes the
parent of Pen. Figure 14.3 shows how the two classes are now
related.
Fig. 14.3 - The relationship between Inventory_item and
Pen.
You may not have noticed, but you have just used the concept of
inheritance. The Pen class inherits two of its properties from
the Inventory_item class. Inheritance is really no more complicated
than that. The child class has the properties of itself plus whatever the parent
class has.
You haven't seen methods or functions used in classes yet. This was
deliberate. Methods are inherited in the same way that data is. However, there
are a couple of tricky aspects of using methods that are better left for later.
Perhaps even until you start looking at Perl code.
Note |
Even though you won't read about methods at this
point in the chapter, there is something important that you need to know
about inheritance and methods. First, methods are inherited just like
properties. Second, using inherited methods helps to create your program
quicker because you are using functionality that is already working.
Therefore - at least in theory - your programs should be easier to
create. |
3.2. Abstract Thinking
Earlier, I mentioned the term abstraction. Let’s examine the idea a little further. In order
to do this, you need a working definition of the term model. How about,
“A model is an approximation of something.” If you build a model car, some of
the items in the original car will be missing, like spark plugs, for example. If
you build a model house, you wouldn’t include the plumbing. Thus, the models
that you build are somewhat abstract; the details don’t matter, just the form.
Abstraction in object-oriented programming works in the same way. As the
programmer, you present the model of your objects to other programmers in the
form of an interface. Actually, the interface is just some documentation
that tells others how to interact with any of your classes. However, nobody
needs to know what your classes really do. It is enough to say that the file
object stores the file name and size and presents the information in English.
Whether the internal format of the information is compressed, Russian, or stored
in memory or on the hard disk is immaterial to the user of your classes.
I recommend that as you design an object or class, you occasionally distance
yourself from the work. Try to view the resulting system through the eyes of
another to check for inconsistencies and relationships that aren't needed.
You've learned about abstraction in abstract terms so far. Now let's use the
Pen class that you created earlier to see a concrete example of
abstraction. The Pen class had only one property of its own, the ink
color (the rest were inherited). For the sake of argument, the ink color can be
"blue", "black", or "red". When a Pen object
is created (the mechanism of creation is unimportant at the moment), a specific
color is assigned to it. Use "blue" for the moment. Here is a line of
code to create the object:
$pen = Pen->new("blue");
Now the
Pen object has been
created. Do you care if the internal format of the ink color is the string
"blue" or the number 1? What if, because you expect to use thousands of
objects, the internal format changes from a string to a number to save computer
memory? As long as the interface does not change, the program that uses the
class does not need to change.
By keeping the external interface of the class fixed, an abstraction is being
used. This reduces the amount of time spent retrofitting programs each time a
change is made to a class the program is using.
3.3. Overriding Methods with Polymorphism
Polymorphism is just a little more complicated than
inheritance because it involves methods. Earlier I said you might not learn
about methods before you look at a real object-oriented Perl program, but I
changed my mind. Let’s make up some methods that belong in an inventory program.
How about a method to print the properties for debugging purposes or a method to
change the quantity-on-hand amount? Figure 14.4 shows the
Inventory_item class with these two functions.
Fig. 14.4 - The Inventory_item class with methods.
This new function is automatically inherited by the PEN class.
However, you will run into a problem because the printProperties()
function won't print the ink color. You have three choices:
- Change the function in the Inventory_item class - This is a bad
choice because the generic inventory item should not know any unique
information about inventory objects - just general or common information.
- Create a new function in the Pen class called
printPenProperties() - This is another bad choice. By solving the
problem this way, every class will soon have its own print functions, and
keeping track of the function names would be a nightmare.
- Create a new function in the Pen class called
printProperties() to override the definition from
Inventory_item - This is a good solution. In fact, this is the way
that polymorphism works.
Perl's take on polymorphism is that if you call a method in your program,
either the current class or a parent class should have defined that method. If
the current class has not defined the method, Perl looks in the parent class. If
the method is still not found, Perl continues to search the class
hierarchy.
I can hear you groaning at this point - another object-oriented word! Yes,
unfortunately. But at least this one uses the normal, everyday definition of the
word. A hierarchy is an organized tree of information. In our examples so
far, you have a two-level hierarchy. It's possible to have class hierarchies
many levels deep. In fact, it's quite common. Figure 14.5 shows a class
hierarchy with more than one level.
Fig. 14.5 - A class hierarchy with many levels.
It's probably worth mentioning that some classes contain only information and
not methods. As far as I know, however, there is no special terminology to
reflect this. These information-only classes may serve as adjunct or helper
classes.
3.4. Keeping Code and Data Together with Encapsulation
There’s not much that I need to say about encapsulation. Keeping the methods in the same place as the information
they affect seems like common sense. It wasn’t done using earlier languages
mostly because the programming tools were not available. The extra work required
to manually perform encapsulation outweighed the benefits that would be gained.
One big advantage of encapsulation is that it makes using information for
unintended purposes more difficult, and this reduces logic errors. For example,
if pens were sold in lots of 100, the changeQuantityOnHand() function
would reflect this. Changing the quantity by only one would not be possible.
This enforcement of business rules is one of the biggest attractions of
object-oriented programming.
3.5. How Perl Handles Objects
Remember the concept of references that was discussed in References? If not, please
re-read it. References will play a large role in the rest of the chapter and are
critical to understanding how classes are used. You specifically need to
remember that the { } notation indicates an anonymous hash. Armed with
this knowledge and the object-oriented terminology from the first part of this
chapter, you are ready to look at real Perl objects. Listing 14.1 shows you how
the inventory_item class could be defined in Perl.
Pseudocode |
Start a new class called Inventory_item. The package
keyword is used to introduce new classes and namespaces.
Define the new() function. This function is responsible for
constructing a new object.
The first parameter to the new() function is the class name
(Inventory_item). This is explained further in the sections
"Example: Initializing Object Properties" and "Static Versus Regular
Methods" later in the chapter.
The bless() function is used to change the data type of the
anonymous hash to $class or Inventory_item. Because this
is the last statement in the method, its value will be returned as the
value of the function. I feel that using the return statement to
explicitly return a value would clutter the code in this situation.
An anonymous hash is used to hold the properties for the class. For the
moment, their values are undefined. Assigning values to properties is
discussed in the section "Example:
Initializing Properties" later in this chapter.
Switch to the package called main. This is the default place
for variables and code to go (technically, this is called a
namespace). If no classes are defined in your script, then this
line is not needed.
Assign an instance of the Inventory_item class to the
$item variable. |
Listing 14.1-14LST01.PL - Defining the Inventory_item
Class |
package Inventory_item;
sub new {
my($class) = shift;
bless {
"PART_NUM" => undef,
"QTY_ON_HAND" => undef
}, $class;
}
package main;
$item = Inventory_item->new();
|
There is a lot of new stuff in this small 10 line listing, and you'll
need to review it carefully to glean the information needed to understand
everything that is happening. You'll also start to translate between the Perl
keywords and the object-oriented terminology.
The first line, package Inventory_item;, says two things, depending
on if you are thinking in terms of objects or in terms of Perl. When considering
objects, it begins the definition of a class. When considering Perl, it means
that a specific namespace will be used.
You read a little bit about namespace in [](./variables.md). A namespace
is used to keep one set of names from interfering with another. For example, you
can have a variable named bar and a function called bar, and
the names will not conflict because variables and functions each have their own
namespace.
The package keyword lets you create your own namespace. This lets
you create more than one function called new() as long as each is in
its own package or namespace. If you need to refer to a specific function in a
specific namespace, you can use Inventory_item->new,
Inventory_item::new, or Inventory_item'new. Which notation you
use will probably depend on your background. Object-oriented folks will probably
want to use the -> notation.
The second line, sub new, starts the definition of a function. It
has become accepted practice in the object-oriented world to construct new
objects with the new() method. This is called the class
constructor. This might be a good time to emphasize that the class
definition is a template. It's only when the new() function is called
that an object is created or instantiated. Instantiation means that
memory is allocated from your computer's memory pool and devoted to the use of
this specific object. The new() function normally returns a reference
to an anonymous hash. Therefore, the new() function should never be
called unless you are assigning its return value to a variable. If you don't
store the reference into a scalar variable for later use, you'll never be able
to access the anonymous hash inside the object. For all intents and purposes,
the anonymous hash is the object.
Note |
Not all objects are represented by hashes. If you
need an object to emulate a gas tank, perhaps an anonymous scalar would be
sufficient to hold the number of gallons of gas left in the tank. However,
you'll see that working with hashes is quite easy once you learn how.
Hashes give you tremendous flexibility to solve programming
problems. |
There is nothing magic about the new function name. You could call the
function that creates new objects create() or build() or
anything else, but don't. The standard is new(), and everyone who reads
your program or uses your classes will look for a new() function. If
they don't find one, confusion might set in. There are so few standards in the
programming business. When they exist, it's usually a good idea to follow them.
The bless() function on the third line changes the data type of its
first parameter to the string value of its second parameter. In the situation
shown here, the data type is changed to the name of the package,
Inventory_item. Using bless() to change the data type of a
reference causes the ref() function to return the new data type. This
potentially confusing point is explained further in the section "Example:
Bless the Hash and Pass the Reference" later in this chapter.
Note |
I used the bless() function without using
parentheses to surround the parameters. While Perl lets you do this, I
have been studiously using parentheses to avoid certain issues of
precedence that seem beyond the scope of this book. In this special
instance, where the anonymous hash is one of the parameters, I feel that
using parentheses clutters the source code. |
Embedded inside the bless() function call is the creation of an
anonymous hash that holds the properties of the class. The hash definition is
repeated here for your convenience:
{
"PART_NUM" => undef,
"QTY_ON_HAND" => undef
};
Nothing significant is happening here that you haven't seen before.
Each entry in the hash is a different property of the class. For the moment, I
have assigned the undefined value to the value part of the entries. Soon you'll
see how to properly initialize them.
After the new() function is defined, there is another package
statement:
package main;
There is no object-oriented way to interpret this
statement. It simply tells Perl to switch back to using the
main
namespace. Don't be fooled into thinking that there is a
main class
somewhere. There isn't.
Caution |
While you could create a main class by
defining the new() function after the package main;
statement, things might get to be confusing, so don't do
it! |
The last statement in the file is really the first line that gets executed.
Everything else in the script have been class and method definitions.
$item = Inventory_item->new();
By now, you've probably
guessed what this statement does. It assigns a reference to the anonymous hash
to
$item. You can dereference
$item in order to determine the
value of the entries in the hash. If you use the
ref() function to
determine the data type of
$item, you find that its value is
Inventory_item.
Here are some key items to remember about objects in Perl:
- All objects are anonymous hashes: While not strictly true, perhaps
it should be. Also, most the examples in this book follow this rule. This
means that most of the new() methods you see return a reference to a
hash. bless() changes the data type of the anonymous hash: The data
type is changed to the name of the class.
- The anonymous hash itself is blessed: This means that references to
the hash are not blessed. This concept is probably a little unclear. I had
trouble figuring it out myself. The next section clarifies this point and uses
an example.
- Objects can belong to only one class at a time: You can use the
bless() function to change the ownership at any time. However, don't
do this unless you have a good reason.
The -> operator is used to call a method associated with a
class: There are two different ways to invoke or call class methods:
$item = new Inventory_item;
or
$item = Inventory_item->new();
Both of these techniques are
equivalent, but the -> style is preferred by object-oriented
folks.
3.5.1. Example: Bless the Hash and Pass the Reference
If you recall from Chapter 8, the ref() function returns either the undefined value or a string
indicating the parameter’s data type (SCALAR, ARRAY,
HASH, CODE, or REF). When classes are used, these
data types don’t provide enough information.
This is why the bless() function was added to the language. It lets
you change the data type of any variable. You can change the data type to any
string value you like. Most often, the data type is changed to reflect the class
name.
It is important to understand that the variable itself will have its data
type changed. The following lines of code should make this clear:
$foo = { };
$fooRef = $foo;
print(“data of $foo is ” . ref(\(foo) . "\n");
print("data of \$fooRef is " . ref(\)fooRef) . “\n”);
bless($foo, “Bar”);
print(“data of $foo is ” . ref(\(foo) . "\n");
print("data of \$fooRef is " . ref(\)fooRef) . “\n”);
This program
displays the following:
data of $foo is HASH
data of $fooRef is HASH
data of $foo is Bar
data of $fooRef is Bar
After the data type is changed, the
ref($fooRef) function call returns
Bar instead of the old
value of
HASH. This can happen only if the variable itself has been
altered. This example also shows that the
bless() function works
outside the object-oriented world.
3.5.2. Example: Initializing Properties
You now know how to instantiate a new class by using a
new() function and how to create class properties (the class
information) with undefined values. Let’s look at how to give those properties
some real values. You need to start by looking at the new() function
from Listing 14.1. It’s repeated here so you don’t need to flip back to look for
it.
sub new {
my($class) = shift;
bless {
"PART_NUM" => undef,
"QTY_ON_HAND" => undef
}, $class;
}
The
new() function is a
static method. Static methods
are not associated with any specific object. This makes sense because the
new() function is designed to create objects. It can't be associated
with an object that doesn't exist yet, can it?
The first argument to a static method is always the class name. Perl takes
the name of the class from in front of the -> operator and adds it
to the beginning of the parameter array, which is passed to the new()
function.
If you want to pass two values into the new() function to initialize
the class properties, you can modify the method to look for additional arguments
as in the following:
sub new {
my($class) = shift;
my($partNum) = shift;
my($qty) = shift;
bless {
"PART_NUM" => $partNum,
"QTY_ON_HAND" => $qty
}, $class;
}
Each parameter you expect to see gets shifted out of the parameter
array into a scalar variable. Then the scalar variable is used to initialize the
anonymous hash.
You invoke this updated version of new() by using this line of code:
$item = Inventory_item->new("AW-30", 1200);
While this style of
parameter passing is very serviceable, Perl provides for the use of another
technique: passing named parameters.
3.5.3. Example: Using Named Parameters in Constructors
The concept of using named parameters has been quickly accepted in new computer languages. I was first introduced to
it while working with the scripting language for Microsoft Word. Rather than
explain the technique in words, let me show you an example in code, as shown in
Listing 14.2. I think you’ll understand the value of this technique very
quickly.
Pseudocode |
Start a definition of the Inventory_item class.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Bless the anonymous hash with the class name.
Use %params to initialize the class properties.
Start the main namespace.
Call the constructor for the Inventory_item class.
Assign the object reference to $item.
Print the two property values to verify that the property
initialization worked. |
Listing 14.2-14LST02.PL - Setting Class Properties Using the Class
Constructor |
package Inventory_item;
sub new {
my($class) = shift;
my(%params) = @_;
bless {
"PART_NUM" => $params{"PART_NUM"},
"QTY_ON_HAND" => $params{"QTY_ON_HAND"}
}, $class;
}
package main;
$item = Inventory_item->new(
“PART_NUM” => “12A-34”,
“QTY_ON_HAND” => 34);
print("The part number is " . %{$item}->{'PART_NUM'} . "\n");
print("The quantity is " . %{$item}->{'QTY_ON_HAND'} . "\n");</B></P></PRE></TT></TD></TR></TBODY></TABLE>
One key statement to understand is the line in which the new()
function is called:
$item = Inventory_item->new(
"PART_NUM" => "12A-34",
"QTY_ON_HAND" => 34); This looks like an associative
array is being passed as the parameter to new(), but looks are
deceiving in this case. The => operator does exactly the same thing
as the comma operator. Therefore, the preceding statement is identical to the
following:
$item = Inventory_item->new("PART_NUM", "12A-34", "QTY_ON_HAND", 34); Also,
a four element array is being passed to new().
The second line of the new() function, my(%params) = @_;
does something very interesting. It takes the four element array and turns it
into a hash with two entries. One entry is for PART_NUM, and the other
is for QTY_ON_HAND.
This conversion (array into hash) lets you access the parameters by name
using %params. The initialization of the anonymous hash - inside the
bless() function - takes advantage of this by using expressions such as
$params{"PART_NUM"}.
I feel that this technique helps to create self-documenting code. When
looking at the script, you always know which property is being referred to. In
addition, you can also use this technique to partially initialize the anonymous
hash. For example,
$item = Inventory_item->new("QTY_ON_HAND" => 34); gives a
value only to the QTY_ON_HAND property; the PART_NUM property will remain
undefined. You can use this technique with any type of function, not just
constructors.
3.5.4. Example: Inheritance, Perl Style
You already know that inheritance means that properties and
methods of a parent class will be available to child classes. This section shows
you can use inheritance in Perl.
First, a little diversion. You may not have realized it yet, but each package
can have its own set of variables that won't interfere with another package's
set. So if the variable $first was defined in package A, you could also
define $first in package B without a conflict arising. For example,
package A;
$first = "package A";
package B;
$first = “package B”;
package main;
print(“\(A::first\n");
print("\)B::first\n”); displays
package A
package B Notice that the :: is being used as a scope
resolution operator in this example. The -> notation will not work;
also, it's okay that -> can't be used because we're not really
dealing with objects in this example, just different namespaces.
You're probably wondering what this diversion has to do with inheritance,
right? Well, inheritance is accomplished by placing the names of parent classes
into a special array called @ISA. The elements of @ISA are
searched left to right for any missing methods. In addition, the
UNIVERSAL class is invisibly tacked on to the end of the search list.
For example,
package UNIVERSAL;
sub AUTOLOAD {
die("[Error: Missing Function] $AUTOLOAD @_\n");
}
package A;
sub foo {
print(“Inside A::foo\n”);
}
package B;
@ISA = (A);
package main;
B->foo();
B->bar(); displays
Inside A::foo
[Error: Missing Function] B::bar B Let's start with the nearly empty
class B. This class has no properties or methods; it just has a parent:
the A class. When Perl executes B->foo(), the first line in
the main package, it first looks in B. When the foo() function
is not found, it looks to the @ISA array. The first element in the
array is A, so Perl looks at the A class. Because A
does have a foo() method, that method is executed.
When a method can't be found by looking at each element of the @ISA
array, the UNIVERSAL class is checked. The second line of the main
package, B->bar(), tries to use a function that is not defined in
either the base class B or the parent class A. Therefore, as a
last ditch effort, Perl looks in the UNIVERSAL class. The
bar() function is not there, but a special function called
AUTOLOAD() is.
The AUTOLOAD() function is normally used to automatically load
undefined functions. Its normal use is a little beyond the scope of this book.
However, in this example, I have changed it into an error reporting tool.
Instead of loading undefined functions, it now causes the script to end (via the
die() function) and displays an error message indicating which method
is undefined and which class Perl was looking in. Notice that the message ends
with a newline to prevent Perl from printing the script name and line number
where the script death took place. In this case, the information would be
meaningless because the line number would be inside the AUTOLOAD()
function.
Listing 14.3 shows how to call the constructor of the parent class. This
example shows how to explicitly call the parent's constructor. In the next
section, you learn how to use the @ISA array to generically call
methods in the parent classes. However, because constructors are frequently used
to initialize properties, I feel that they should always be called explicitly,
which causes less confusion when calling constructors from more than one parent.
This example also shows how to inherit the properties of a parent class. By
calling the parent class constructor function, you can initialize an anonymous
hash that can be used by the base class for adding additional properties.
Pseudocode |
Start a definition of the Inventory_item class.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Bless the anonymous hash with the class name.
Use %params to initialize the class properties.
Start a definition of the Pen class.
Initialize the @ISA array to define the parent classes.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Call the constructor for the parent class, Inventory_item, and
assign the resulting object reference to $self.
Create an entry in the anonymous hash for the INK_COLOR key.
Bless the anonymous hash so that ref() will return
Pen and return a reference to the anonymous hash.
Start the main namespace.
Call the constructor for the Pen class. Assign the object
reference to $item. Note that an array with property-value pairs
are passed to the constructor.
Print the three property values to verify that the property
initialization worked. |
Listing 14.3-14LST03.PL - How to Call the Constructor of a Parent
Class |
package Inventory_item;
sub new {
my($class) = shift;
my(%params) = @_;
bless {
"PART_NUM" => $params{"PART_NUM"},
"QTY_ON_HAND" => $params{"QTY_ON_HAND"}
}, $class;
}
package Pen;
@ISA = (Inventory_item);
sub new {
my($class) = shift;
my(%params) = @_;
my($self) = Inventory_item->new(@_);
$self->{"INK_COLOR"} = $params{"INK_COLOR"};
return(bless($self, $class));
}
package main;
$pen = Pen->new(
“PART_NUM” => “12A-34”,
“QTY_ON_HAND” => 34,
“INK_COLOR” => “blue”);
print("The part number is " . %{$pen}->{'PART_NUM'} . "\n");
print("The quantity is " . %{$pen}->{'QTY_ON_HAND'} . "\n");
print("The ink color is " . %{$pen}->{'INK_COLOR'} . "\n");</B></P></PRE></TT></TD></TR></TBODY></TABLE>
This program displays:
The part number is 12A-34
The quantity is 34
The ink color is blue You should be familiar with all the aspects of
this script by now. The line my($self) = Inventory_item->new(@_); is
used to get a reference to an anonymous hash. This hash becomes the object for
the base class.
To understand that calling the parent constructor creates the object that
becomes the object for the base class, you must remember that an object
is the anonymous hash. Because the parent constructor creates the
anonymous hash, the base class only needs a reference to that hash in order to
add its own properties. This reference is stored in the $self variable.
You may also see the variable name $this used to hold the reference
in some scripts. Both $self and $this are acceptable in the
object-oriented world.
Note |
I would actually prefer the variable name
$data because the hash is the object; therefore, the data
is the object. But sometimes, it's good to follow conventional
wisdom so that others can more easily understand your
programs. |
3.5.5. Example: Polymorphism
Polymorphism, although a big word, is a simple
concept. It means that methods defined in the base class will override methods
defined in the parent classes. The following small example clarifies this
concept:
package A;
sub foo {
print("Inside A::foo\n");
}
package B;
@ISA = (A);
sub foo {
print("Inside B::foo\n");
}
package main;
B->foo(); This program displays
Inside B::foo The foo() defined in class B
overrides the definition that was inherited from class A.
Polymorphism is mainly used to add or extend the functionality of an existing
class without reprogramming the whole class. Listing 14.4 uses polymorphism to
override the qtyChange() function inherited from
Inventory_item. In addition, it shows how to call a method in a parent
class when the specific parent class name (also known as the SUPER class)
is unknown.
Pseudocode |
Start a definition of the Inventory_item class.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Bless the anonymous hash with the class name.
Use %params to initialize the class properties.
Define the qtyChange() method.
Get the object reference from the parameter array.
Get the quantity to change from the parameter array. If there are no
more elements in the @_, default to using the quantity 1.
Use dereferencing to change the QTY_ON_HAND property.
Start a definition of the Pen class.
Initialize the @ISA array to define the parent classes.
Initialize the @PARENT::ISA array to let Perl search the @ISA
to look for method references.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Call the constructor for the parent class using the PARENT::
notation. This searches the classes listed in the @ISA array
looking for the new() function and assigns the resulting object
reference to $self.
Create an entry in the anonymous hash for the INK_COLOR key.
Return a reference to the anonymous hash.
Define the qtyChange() method.
Get the object reference from the parameter array.
Get the quantity to change from the parameter array. If there are no
more elements in the @_, default to using the quantity 100.
Use dereferencing to change the QTY_ON_HAND property.
Start the main namespace.
Call the constructor for the Pen class. Assign the object
reference to $item.
Print the data type of $item to show that it is now
Pen.
Print the three property values to verify that the property
initialization worked.
Change the quantity by the default amount.
Print a newline to separate the previous values from the new value.
Print the quantity property value to verify that the change method
worked. |
Listing 14.4-14LST04.PL - Accessing Methods in Parent
Classes |
package Inventory_item;
sub new {
my($class) = shift;
my(%params) = @_;
bless {
"PART_NUM" => $params{"PART_NUM"},
"QTY_ON_HAND" => $params{"QTY_ON_HAND"}
}, $class;
}
sub qtyChange {
my($self) = shift;
my($delta) = $_[0] ? $_[0] : 1;
$self->{"QTY_ON_HAND"} += $delta;
}
package Pen;
@ISA = (“Inventory_item”);
@PARENT::ISA = @ISA;
sub new {
my($class) = shift;
my(%params) = @_;
my($self) = $class->PARENT::new(@_);
$self->{"INK_COLOR"} = $params{"INK_COLOR"};
return($self);
}
sub qtyChange {
my($self) = shift;
my($delta) = $_[0] ? $_[0] : 100;
$self->PARENT::qtyChange($delta);
}
package main;
$pen = Pen->new(
"PART_NUM"=>"12A-34",
"QTY_ON_HAND"=>340,
"INK_COLOR" => "blue");
print("The data type is " . ref($pen) . "\n");
print("The part number is " . %{$pen}->{'PART_NUM'} . "\n");
print("The quantity is " . %{$pen}->{'QTY_ON_HAND'} . "\n");
print("The ink color is " . %{$pen}->{'INK_COLOR'} . "\n");
$pen->qtyChange();
print("\n");
print("The quantity is " . %{$pen}->{'QTY_ON_HAND'} . "\n");</B></P></PRE></TT></TD></TR></TBODY></TABLE>
This program displays
The data type is Pen
The part number is 12A-34
The quantity is 340
The ink color is blue
The quantity is 440 The first interesting line in the preceding example
is my(\(delta) = \)_[0] ? \(_[0] : 1;</TT>. This line checks to see if a
parameter was passed to <TT>Inventory_item::qtychange()</TT> and if not, assigns
a value of 1 to <TT>\)delta. This line of code uses the ternary
operator to determine if \(_[0]</TT> has a value or not. A zero is used as
the subscript because the class reference was shifted out of the parameter array
and into <TT>\)self.
The next interesting line is @PARENT::ISA = @ISA;. This assignment
lets you refer to a method defined in the parent class. Perl searches the parent
hierarchy (the @ISA array) until a definition is found for the
requested function.
The Pen::new() function uses the @PARENT::ISA to find the
parent constructor using this line: my($self) =
$class->PARENT::new(@_);. I don't really recommend calling parent
constructors in this manner because the constructor that gets called will depend
on the order of classes in the @ISA array. Having code that is
dependent on an array keeping a specific order is a recipe for disaster; you
might forget about the dependency and spend hours trying to find the problem.
However, I thought you should see how it works. Because the $class
variable (which is equal to Pen) is used to locate the parent
constructor, the hash will be blessed with the name of the base Pen
class - one small advantage of this technique. This is shown by the program's
output. This technique avoids having to call the bless() function in
the base class constructor.
By now, you must be wondering where polymorphism fits into this example.
Well, the simple fact that both the Pen and Inventory_item
classes have the qtyChange() method means that polymorphism is being
used. While the Inventory_item::qtyChange() method defaults to changing
the quantity by one, the Pen::qtyChange() method defaults to changing
the quantity by 100. Because the Pen::qtyChange() method simply
modifies the behavior of Inventory_item::qtyChange(), it does not need
to know any details about how the quantity is actually changed. This capability
to change functionality without knowing the details is a sign that abstraction
is taking place.
Tip |
The Inventory_item::qtychange() notation
refers to the qtyChange() function in the Inventory_item
class, and Pen::qtyChange() refers to the qtyChange()
function in the Pen class. This notation lets you uniquely
identify any method in your script. |
3.5.6. Example: How One Class Can Contain Another
Now that you have seen several objects in action,
you probably realize that some class properties will be objects themselves. For
example, you might have a billing object that contains an inventory object, or
you might use a car object inside a warehouse object. The possibilities are
endless.
Listing 14.5 shows how to add a color object to the inventory system you've
been building. It also shows you that Perl will execute statements that are not
part of a function - even those in packages other than main - as soon as they
are seen by the interpreter.
Pseudocode |
Start a definition of the Inventory_item class.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Bless the anonymous hash with the class name.
Use %params to initialize the class properties.
Start a definition of the Pen class.
Initialize the @ISA array to define the parent classes.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Call the constructor for the parent class and assign the resulting
object reference to $self.
Create an entry in the anonymous hash for the INK_COLOR key by
calling the constructor for the Color class.
Return a reference to the anonymous hash that has been blessed into the
Pen class.
Start a definition of the Color class.
Print a message on STDOUT.
Create two entries in the %Colors hash.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Assign a reference to one of the entries in the %Colors hash
to $self. This will be used as the object reference.
Bless the hash entry into the Color class and return
$self as the object reference.
Start the main namespace.
Print a message on STDOUT.
Call the constructor for the Pen class. Assign the object
reference to $item.
Use %properties as a temporary value to simplify the
dereferencing process.
Print the three property values to verify that the property
initialization worked. |
Listing 14.5-14LST05.PL - How One Class Can Use or Contain Another
Class |
package Inventory_item;
sub new {
my($class) = shift;
my(%params) = @_;
bless {
"PART_NUM" => $params{"PART_NUM"},
"QTY_ON_HAND" => $params{"QTY_ON_HAND"}
}, $class;
}
package Pen;
@ISA = (Inventory_item);
sub new {
my($class) = shift;
my(%params) = @_;
my($self) = Inventory_item->new(@_);
$self->{"INK_COLOR"} = Color->new($params{"INK_COLOR"});
return(bless($self, $class));
}
package Color;
print(“Executing Color statements\n”);
\(colors{"blue"} = "Die Lot 13";
\)colors{“red”} = “Die Lot 5”;
sub new {
my($class) = shift;
my($param) = @_;
my($self) = \$colors{$param};
return(bless($self, $class));
}
package main;
print(“Executing main statements\n”);
$pen = Pen->new(
"PART_NUM" => "12A-34",
"QTY_ON_HAND" => 34,
"INK_COLOR" => "blue");
%properties = %{$pen};
print("The part number is " . $properties{'PART_NUM'} . "\n");
print("The quantity is " . $properties{'QTY_ON_HAND'} . "\n");
print("The ink color is " . ${$properties{'INK_COLOR'}} . "\n");</B></P></PRE></TT></TD></TR></TBODY></TABLE>
This program displays
Executing Color statements
Executing main statements
The part number is 12A-34
The quantity is 34
The ink color is Die Lot 13 Where to start? You already know about the
Inventory_item class and the @ISA array. Let's look at the
assignment to the INK_COLOR entry of the Pen class. This line,
$self->{"INK_COLOR"} = Color->new($params{"INK_COLOR"});, is used
to call the constructor for the Color class. The expression
$params{"INK_COLOR"} passes the value of "blue" to the
Color constructor, which returns a reference to one of the colors in
the %colors associative array.
You can tell that Perl executes all statements that are not inside functions
because the print statement in the Color package is executed
before the print statement in the main package. This is why
you can define hash entries inside the Color class. When variables are
defined inside a package but outside a function, they are called static
variables. You can access one of the hash entries in the Color package
like this: $Color::colors{"blue"}.
3.6. Static Versus Regular Methods and Variables
You already learned that a static method is one that can be called without needing an instantiated object. Actually, you can
also have static variables as you saw in the last section. Static variables can
be used to emulate constants, values that don’t change. Constants are
very useful. For example, you can use them for tax rates, mathematical
constants, and things like state abbreviations. Here is an example using a small
Perl script:
package Math;
$math{'PI'} = 3.1415;
package main;
print(“The value of PI is $Math::math{‘PI’}.\n”); This program
displays
The value of PI is 3.1415. You can also do this:
package Math;
$PI = 3.1415;
package main;
print("The value of PI is $Math::PI.\n"); Because you have been
using a static method all along - the new() method - I'll take this
opportunity to demonstrate a regular function. Listing 14.6 shows how to use the
UNIVERSAL package to define a utility function that is available to all
classes.
Pseudocode |
Start a definition of the UNIVERSAL class.
Define the lookup() method.
Dereference the object reference (the first element of @_) and
use the second parameter as the key into the anonymous hash. Return the
value of the hash entry.
Start a definition of the Inventory_item class.
Define the constructor for the class.
Get the name of the class from the parameter array.
Assign the rest of the parameters to the %params hash.
Bless the anonymous hash with the class name.
Use %params to initialize the class properties.
Start the main namespace.
Call the constructor for the Inventory_item class. Assign the
object reference to $item.
Print the two property values using the lookup() method to
verify that the property initialization
worked. |
Listing 14.6-14LST06.PL - Using a Static Method to Retrieve Class
Properties |
package UNIVERSAL;
sub lookup {
return(%{$_[0]}->{$_[1]});
}
package Inventory_item;
sub new {
my(\(class) = shift;
my(%params) = @_;
my(\)self) = { };
$self->{"PART_NUM"} = $params{"PART_NUM"};
$self->{"QTY_ON_HAND"} = $params{"QTY_ON_HAND"};
return(bless($self, $class));
}
package main;
$item = Inventory_item->new("PART_NUM"=>"12A-34", "QTY_ON_HAND"=>34);
print("The part number is " . $item->lookup('PART_NUM') . "\n");
print("The quantity is " . $item->lookup('QTY_ON_HAND') . "\n");</B></P></PRE></TT></TD></TR></TBODY></TABLE>
I don't think this example needs any further explanation, so let's use the
space normally reserved to further discussion of the listing and show you
another utility function instead. The printAll() function shown here
displays all the properties of a class, or you can specify one or more
properties to display:
sub printAll {
my($self) = shift;
my(@keys) = @_ ? @_ : sort(keys(%{$self}));
print("CLASS: $self\n");
foreach $key (@keys) {
printf("\t%10.10s => $self->{$key}\n", $key);
}
} If you put this function into the UNIVERSAL package, it will
be available to any classes you define.
After constructing an inventory object, the statement
$item->printAll(); might display
CLASS: Inventory_item=HASH(0x77ceac)
PART_NUM => 12A-34
QTY_ON_HAN => 34 and the statement
$item->printAll('PART_NUM'); might display
CLASS: Inventory_item=HASH(0x77ceac)
PART_NUM => 12A-34
3.7. Summary
This chapter served as an introduction to objects. It was not intended to turn you into an overnight object guru. I hope
that enough information was presented so you have an understanding of the object
terminology and can read other people’s programs. You can also create your own
methods and properties. However, if you need to create more than a few small
objects, consider reading a book devoted specifically to object-oriented
programming. I give this advice because the relationships between objects can
become complex quickly when more than five objects are being used.
You learned earlier in the chapter that object-oriented programming has its
own terminology. This terminology lets you think of objects in a computer
language independent manner. After describing the object or class as a set of
properties (information) and methods (functions), the class can be programmed
using C++, Perl, or Delphi. The programming language is relegated to the role of
an implementation detail.
The four big concepts in object-oriented programming are abstraction,
encapsulation, inheritance, and polymorphism. Abstraction means to isolate the
access of a property from how it's stored. Encapsulation means that properties
and the methods that act on them are defined together. Inheritance means that
one class (the child) can be derived from another (the parent), and the child
class will have all the properties and methods defined in the parent.
Polymorphism means that the child class can override properties and methods
defined in the parent simply by using the same property or method name.
After defining these words, you read about creating some classes for an
inventory system; the Inventory_item and Pen classes were
described. The Pen class was derived from the Inventory_item
class. These classes were used in examples to show how abstraction and
polymorphism work.
Next, you looked at object-oriented Perl scripts. You read that it's good to
keep all class property information in anonymous hashes and that the
bless() function is used to change the data type of a variable - even
anonymous ones.
You saw how to initialize properties by passing values to the new()
constructor function. With this technique, you can use named parameters and
therefore create partially initialized objects if needed. Child classes in Perl
will not automatically inherit properties from its parents. However, using
anonymous hashes totally avoids this issue because the parent constructor can be
explicitly called to create the object. Then, the child can simply add entries
to the anonymous hash.
You saw an example of how one class can contain another. The Pen
class used this technique to hold an instance of the Color class.
Static variables and methods are independent of any specific object. For
example, the Color class used a static hash to hold values for the
colors blue and red. Static variables can be accessed using the notation
$Color::colors{"blue"}. Of course, only static hash variables use this
notation, but scalars and arrays are accessed similarly. You can use static
methods like new() to create new instances of a class.
You also saw that the @ISA array is used to hold a list of parent
classes for the base class. In addition, you learned that the UNIVERSAL
class is invisibly added to the end of the @ISA array - making it the
the last class searched for an undefined method. The AUTOLOAD() method
is normally used to load undefined methods; however, in this chapter, it was
used instead to display an error message telling which method is undefined and
the base class in which it should be defined.
The next chapter discusses modules. You see that classes are a specific use
of the general module functionality and how to store module (and class)
definition in different script files. You also see how to use some of the
prewritten modules available in your Perl distribution files and on the
Internet.
3.8. Review Questions
- What is an object?
- What is a class?
- What is a property?
- What does the term polymorphism mean?
- Is the bless() function used to create classes?
- What does the package keyword do?
- What can a static variable be used for?
- Why is it good to use anonymous hashes to represent objects instead of
regular arrays?
- How can you create a function that is available to all classes in your
script?
3.9. Review Exercises
- Design an object model of a car. Create objects for the car, tires, and
doors.
- Extend the inventory model shown in Figure 14.3 to include three other
objects.
- Extend the program in Listing 14.2 to add a third property to the
Pen class.
- Extend the car model from Exercise 1 to include motorcycle objects. Pay
special attention to assumptions you may have made in your original model. Are
these still valid assumptions?
- By using the program in Listing 14.2, create a child of Pen that
can hold two different ink colors.
|
| | |
|