| |||
Links Sections Overriding Methods with Polymorphism Keeping Code and Data Together with Encapsulation Static Versus Regular Methods and Variables Chapters Part I: Basic Perl 02-Numeric and String
Literals Part II: Intermediate Perl Part III: Advanced Perl 13-Handling Errors and
Signals Part IV: Perl and the Internet 21-Using Perl with Web
Servers Appendixes |
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.
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, 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. |
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.
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:
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.
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.
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 |
|
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 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 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:
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.
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.
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.
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 |
|
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.
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 |
|
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. |
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 |
|
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. |
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 |
|
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"}.
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 |
|
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
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.