We've looked at several approaches to accessing per-object data values. Sometimes, though, you want some common state shared by all objects of a class. Instead of being an attribute of just one instance of the class, these variables are global to the entire class, no matter which class instance (object) you use to access them through. (C++ programmers would think of these as static member data.) Here are some situations where class variables might come in handy:
To keep a count of all objects ever created, or how many are still kicking around.
To keep a list of all objects over which you can iterate.
To store the name or file descriptor of a log file used by a class-wide debugging method.
To keep collective data, like the total amount of cash dispensed by all ATMs in a network in a given day.
To track the last object created by a class, or the most accessed object.
To keep a cache of in-memory objects that have already been reconstituted from persistent memory.
To provide an inverted lookup table so you can find an object based on the value one of its attributes.
The question comes down to deciding where to store the state for those shared attributes. Perl has no particular syntactic mechanism to declare class attributes, any more than it has for instance attributes. Perl provides the developer with a broad set of powerful but flexible features that can be uniquely crafted to the particular demands of the situation. You can then select the mechanism that makes the most sense for the given situation instead of having to live with someone else's design decisions. Alternatively, you can live with the design decisions someone else has packaged up and put onto CPAN. Again, TMTOWTDI.
Like anything else pertaining to a class, class data shouldn't be accessed directly, especially from outside the implementation of the class itself. It doesn't say much for encapsulation to set up carefully controlled accessor methods for instance variables but then invite the public in to diddle your class variables directly, such as by setting $SomeClass::Debug = 1. To establish a clear firewall between interface and implementation, you can create accessor methods to manipulate class data similar to those you use for instance data.
Imagine we want to keep track of the total world population of Critter objects. We'll store that number in a package variable, but provide a method called population so that users of the class don't have to know about the implementation.
Critter->population() # Access via class name $gollum->population() # Access via instance
Since a class in Perl is just a package, the most natural place to store class data is in a package variable. Here's a simple implementation of such a class. The population method ignores its invocant and just returns the current value of the package variable, $Population. (Some programmers like to capitalize their globals.)
If you want to make class data methods that work like accessors for instance data, do this:package Critter; our $Population = 0; sub population { return $Population; } sub DESTROY { $Population-- } sub spawn { my $invocant = shift; my $class = ref($invocant) || $invocant; $Population++; return bless { name => shift || "anon" }, $class; } sub name { my $self = shift; $self->{name} = shift if @_; return $self->{name}; }
Now you can set the overall debug level through the class or through any of its instances.our $Debugging = 0; # class datum sub debug { shift; # intentionally ignore invocant $Debugging = shift if @_; return $Debugging; }
Because it's a package variable, $Debugging is globally accessible. But if you change the our variable to my, then only code later in that same file can see it. You can go still further--you can restrict unfettered access to class attributes even from the rest of class itself. Wrap the variable declaration in a block scope:
Now no one is allowed to read or write the class attributes without using the accessor method, since only that subroutine is in the same scope as the variable and has access to it.{ my $Debugging = 0; # lexically scoped class datum sub debug { shift; # intentionally ignore invocant $Debugging = shift if @_; return $Debugging; } }
If a derived class inherits these class accessors, then these still access the original data, no matter whether the variables were declared with our or my. The data isn't package-relative. You might look at it as methods executing in the class in which they were originally defined, not in the class that invoked them.
For some kinds of class data, this approach works fine, but for others, it doesn't. Suppose we create a Warg subclass of Critter. If we want to keep our populations separate, Warg can't inherit Critter's population method, because that method as written always returns the value of $Critter::Population.
You'll have to decide on a case-by-case basis whether it makes any sense for class attributes to be package relative. If you want package-relative attributes, use the invocant's class to locate the package holding the class data:
We temporarily rescind strict references because otherwise we couldn't use the fully qualified symbolic name for the package global. This is perfectly reasonable: since all package variables by definition live in a package, there's nothing wrong with accessing them via that package's symbol table.sub debug { my $invocant = shift; my $class = ref($invocant) || $invocant; my $varname = $class . "::Debugging"; no strict "refs"; # to access package data symbolically $$varname = shift if @_; return $$varname; }
Another approach is to make everything an object needs--even its global class data--available via that object (or passed in as parameters). To do this, you'll often have to make a dedicated constructor for each class, or at least have a dedicated initialization routine to be called by the constructor. In the constructor or initializer, you store references to any class data directly in the object itself, so nothing ever has to go looking for it. The accessor methods use the object to find a reference to the data.
Rather than put the complexity of locating the class data in each method, just let the object tell the method where the data is located. This approach works well only when the class data accessor methods are invoked as instance methods, because the class data could be in unreachable lexicals you couldn't get at using a package name.
No matter how you roll it, package-relative class data is always a bit awkward. It's really a lot cleaner if, when you inherit a class data accessor method, you effectively inherit the state data that it's accessing as well. See the perltootc manpage for numerous, more elaborate approaches to management of class data.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
HIVE: All information for read only. Please respect copyright! |