Документация
HTML CSS PHP PERL другое

Section 14.2.  Testing Our Objects for Good Behavior

 
Previous
Table of Contents
Next

14.2. Testing Our Objects for Good Behavior

Besides providing a place for us to put universally available methods, the UNIVERSAL package comes preloaded with two very useful utility methods: isa and can. Because UNIVERSAL defines these methods, they are available to all objects.

The isa method tests to see whether a given class or instance is a member of a given class or a member of a class that inherits from the given class. For example, continuing on with the Animal family from the previous chapters:

if (Horse->isa('Animal')) {    # does Horse inherit from Animal?
  print "A Horse is an Animal.\n";
}

my $tv_horse = Horse->named("Mr. Ed");
if ($tv_horse->isa('Animal')) { # is it an Animal?
  print $tv_horse->name, " is an Animal.\n";
  if ($tv_horse->isa('Horse')) { # is it a Horse?
    print 'In fact, ', $tv_horse->name, " is a Horse.\n";
  } else {
    print "...but it's not a Horse.\n";
  }
}

This is handy when we have a heterogeneous mix of objects in a data structure and want to distinguish particular categories of objects:

my @horses = grep $_->isa('Horse'), @all_animals;

The result will be only the horses (or racehorses) from the array. We compare that with:

my @horses_only = grep ref $_ eq 'Horse', @all_animals;

which picks out just the horses, because a RaceHorse won't return Horse for ref.

In general, we shouldn't use:

ref($some_object) eq 'SomeClass'

in our programs because it prevents future users from subclassing that class. Use the isa construct as given earlier.

One downside of the isa call here is that it works only on blessed references or scalars that look like class names. If we happen to pass it an unblessed reference, we get a fatal (but trappable) error of:

Can't call method "isa" on unblessed reference at ...

To call isa more robustly, we could call it as a subroutine:

if (UNIVERSAL::isa($unknown_thing, 'Animal')) {
  ... it's an Animal! ...
}

This runs without an error, no matter what $unknown_thing contains. But it's subverting the OO mechanism, which has its own set of problems.[*] This is a job for an exception mechanism, which is eval. If the value in $unknown_thing isn't a reference, then we can't call a method on it. The eval TRaps that error and returns undef, which is false.

[*] Particularly, if Animal has a custom isa method (perhaps it rejects a mutant branch of talking animals in the family tree), calling UNIVERSAL::isa skips past Animal::isa and may give you the wrong answer.

if (eval { $unknown_thing->isa('Animal') }) {
  ... it's an Animal ...
}

As in the case of isa, we can test for acceptable behaviors with the can method. For example:

if ($tv_horse->can('eat')) {
  $tv_horse->eat('hay');
}

If the result of can is true, then somewhere in the inheritance hierarchy, a class claims it can handle the eat method. Again, the caveats about $tv_horse being only either a blessed reference or a class name as a scalar still apply, so the robust solution when we might deal with nearly anything looks like:

if (eval { $tv_horse->can('eat') } ) { ... }

Note that if we defined UNIVERSAL::fandango earlier, then:

$object->can('fandango')

always returns true, because all objects can do the fandango.


Previous
Table of Contents
Next
© 2000- NIV