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 such as 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.
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 Chapter 10, "Regular Expressions"? 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, depends 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.
Figure 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.
Figure 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.
Figure 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 more quickly because you are using functionality that is already working. Therefore-at least in theory-your programs should be easier to create. |
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, such as 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.
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.
Figure 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:
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.
Figure 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.
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.
Remember the concept of references that was discussed in Chapter 8, "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.
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 ten-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 Chapter 3 "Variables." 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 has 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 of 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.
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.
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.
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.
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");
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.
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.
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 is 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");
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 needs a reference only 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. |
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.
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");
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;. This line checks to see if a parameter was passed to Inventory_item::qtychange() and if not, assigns a value of 1 to $delta. This line of code uses the ternary operator to determine if $_[0] 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 $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. |
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.
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");
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"}.
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 such as 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.
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.
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");
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
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.
Answers to Review Questions are in Appendix A.