Perl/FAQ/Классы, Объекты и связи

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Perl ·

Содержание

[править] Introduction

#-----------------------------
$object = {};                       # hash reference
bless($object, "Data::Encoder");    # bless $object into Data::Encoder class
 
bless($object);                     # bless $object into current package
#-----------------------------
$obj = [3,5];
print ref($obj), " ", $obj->[1], "\n";
 
bless($obj, "Human::Cannibal");
print ref($obj), " ", $obj->[1], "\n";
 
ARRAY 5
 
Human::Cannibal 5
#-----------------------------
$obj->{Stomach} = "Empty";   # directly accessing an object's contents
 
$obj->{NAME}    = "Thag";        # uppercase field name to make it stand out (optional)
#-----------------------------
$encoded = $object->encode("data");
#-----------------------------
 
$encoded = Data::Encoder->encode("data");
#-----------------------------
sub new {
    my $class = shift;
 
    my $self  = {};         # allocate new hash for object
    bless($self, $class);
    return $self;
 
}
#-----------------------------
$object = Class->new();
#-----------------------------
$object = Class::new("Class");
#-----------------------------
sub class_only_method {
 
    my $class = shift;
    die "class method called on object" if ref $class;
 
    # more code here
} 
#-----------------------------
sub instance_only_method {
    my $self = shift;
 
    die "instance method called on class" unless ref $self;
    # more code here
} 
 
#-----------------------------
$lector = new Human::Cannibal;
feed $lector "Zak";
move $lector "New York";
#-----------------------------
$lector = Human::Cannibal->
new();
 
$lector->feed("Zak");
$lector->move("New York");
#-----------------------------
printf STDERR "stuff here\n";
 
#-----------------------------
move $obj->{FIELD};                 # probably wrong
move $ary[$i];                      # probably wrong
#-----------------------------
$obj->move->{FIELD};                # Surprise!
 
$ary->move->[$i];                   # Surprise!
#-----------------------------
$obj->{FIELD}->
move()
;              # Nope, you wish
 
$ary[$i]->
move;
                     # Nope, you wish
#-----------------------------

[править] Constructing an Object

#-----------------------------
sub new {
 
    my $class = shift;
    my $self  = { };
 
    bless($self, $class);
    return $self;
} 
#-----------------------------
sub new { bless( { }, shift ) }
 
#-----------------------------
sub new { bless({}) }
#-----------------------------
sub new {
 
    my $self = { };  # allocate anonymous hash
    bless($self);
 
    # init two sample attributes/data members/fields
    $self->{START} = time();  
    $self->{AGE}   = 0;
 
    return $self;
} 
#-----------------------------
sub new {
    my $classname  = shift;         # What class are we constructing?
 
    my $self      = {};             # Allocate new memory
    bless($self, $classname);       # Mark it of the right type
    $self->{START}  = 
 
time();
       # init data fields
    $self->{AGE}    = 
0;
 
    return $self;                   # And give it back
} 
#-----------------------------
sub new {
 
    my $classname  = shift;         # What class are we constructing?
    my $self      = {};             # Allocate new memory
 
    bless($self, $classname);       # Mark it of the right type
    $self->_init(@_);               # Call _init with remaining args
    return $self;
 
} 
 
# "private" method to initialize fields.  It always sets START to
# the current time, and AGE to 0.  If called with arguments, _init
# interprets them as key+value pairs to initialize the object with.
sub _init {
    my $self = shift;
 
    $self->{START} = 
time();
 
    $self->{AGE}   = 0;
 
    if (@_) {
        my %extra = @_;
 
        @$self{keys %extra} = values %extra;
    } 
 
} 
#-----------------------------

[править] Destroying an Object

#-----------------------------
sub DESTROY {
    my $self = shift;
 
    printf("$self dying at %s\n", scalar localtime);
} 
#-----------------------------
 
$self->{WHATEVER} = $self;
 
#-----------------------------

[править] Managing Instance Data

#-----------------------------
sub get_name {
    my $self = shift;
 
    return $self->{NAME};
} 
 
sub set_name {
 
    my $self      = shift;
    $self->{NAME} = shift;
 
} 
#-----------------------------
sub name {
    my $self = shift;
 
    if (@_) { $self->{NAME} = shift } 
 
    return $self->{NAME};
} 
#-----------------------------
sub age {
 
    my $self = shift;
    my $prev = $self->{AGE};
 
    if (@_) { $self->{AGE} = shift } 
 
    return $prev;
} 
# sample call of get and set: happy birthday!
$obj->age( 1 + $obj->age );
 
#-----------------------------
$him = Person->
new()
;
$him->{NAME} = "Sylvester";
 
$him->{AGE}  = 23;
#-----------------------------
use Carp;
sub name {
 
    my $self = shift;
    return $self->{NAME} unless @_;
 
    local $_ = shift;
    croak "too many arguments" if @_;
 
    if ($^W) {
        /[^\s\w'-]/         && carp "funny characters in name"; #'
 
        /\d/                && carp "numbers in name";
        /\S+(\s+\S+)+/      || carp "prefer multiword name";
        /\S/                || carp "name is blank";
 
    } 
    s/(\w+)/\u\L$1/g;       # enforce capitalization
    $self->{NAME} = $_;
 
} 
#-----------------------------
package Person;
 
# this is the same as before...
sub new {
 
     my $that  = shift;
     my $class = ref($that) || $that;
 
     my $self = {
           NAME  => undef,
           AGE   => undef,
           PEERS => [],
    };
 
    bless($self, $class);
    return $self;
}
 
use Alias qw(attr);
 
use vars qw($NAME $AGE @PEERS);
 
sub name {
 
    my $self = attr shift;
    if (@_) { $NAME = shift; }
 
    return    $NAME;
};
 
sub age {
    my $self = attr shift;
 
    if (@_) { $AGE = shift; }
    return    $AGE;
 
}
 
sub peers {
    my $self = attr shift;
 
    if (@_) { @PEERS = @_; }
 
    return    @PEERS;
}
 
sub exclaim {
    my $self = attr shift;
 
    return sprintf "Hi, I'm %s, age %d, working with %s",
            $NAME, $AGE, join(", ", @PEERS);
 
}
 
sub happy_birthday {
    my $self = attr shift;
 
    return ++$AGE;
}
#-----------------------------

[править] Managing Class Data

#-----------------------------
package Person;
 
$Body_Count = 0; 
 
sub population { return $Body_Count }
 
sub new {                                   # constructor
    $Body_Count++;
    return bless({}, shift);
 
}
 
sub DESTROY { --$BodyCount }                # destructor
 
# later, the user can say this:
package main;
 
for (1..10) { push @people, Person->new }
 
printf "There are %d people alive.\n", Person->population();
 
There are 10 people alive.
#-----------------------------
$him = Person->
 
new()
;
$him->gender("male");
 
$her = Person->
new()
 
;
$her->gender("female");
#-----------------------------
FixedArray->Max_Bounds(100);                # set for whole class
$alpha = FixedArray->new();
 
printf "Bound on alpha is %d\n", $alpha->Max_Bounds();
100
 
$beta = FixedArray->new();
$beta->Max_Bounds(50);                      # still sets for whole class
 
printf "Bound on alpha is %d\n", $alpha->Max_Bounds();
50
#-----------------------------
package FixedArray;
 
$Bounds = 7;  # default
sub new { bless( {}, shift ) }
 
sub Max_Bounds {
    my $proto  = shift;
    $Bounds    = shift if @_;          # allow updates
 
    return $Bounds;
} 
#-----------------------------
sub Max_Bounds { $Bounds }
 
#-----------------------------
sub new {
    my $class = shift;
    my $self = bless({}, $class);
 
    $self->{Max_Bounds_ref} = \$Bounds;
    return $self;
} 
 
#-----------------------------

[править] Using Classes as Structs

#-----------------------------
use Class::Struct;          # load struct-building module
 
struct Person => {          # create a definition for a "Person"
 
    name   => '$',          #    name field is a scalar
    age    => '$',          #    age field is also a scalar
    peers  => '@',          #    but peers field is an array (reference)
 
};
 
my $p = Person->
new()
;      # allocate an empty Person struct
 
$p->name("Jason Smythe");                   # set its name field
 
$p->age(13);                                # set its age field
$p->peers( ["Wilbur", "Ralph", "Fred" ] );  # set its peers field
 
 
# or this way:
@{$p->peers} = ("Wilbur", "Ralph", "Fred");
 
# fetch various values, including the zeroth friend
printf "At age %d, %s's first friend is %s.\n",
    $p->age, $p->name, $p->peers(0);
 
#-----------------------------
use Class::Struct;
 
struct Person => {name => '$',      age  => '$'};  #'
 
struct Family => {head => 'Person', address => '$', members => '@'};  #'
 
$folks  = Family->
new();
 
$dad    = $folks->head;
$dad->name("John");
 
$dad->age(34);
 
printf("%s's age is %d\n", $folks->head->name, $folks->head->age);
 
#-----------------------------
sub Person::age {
    use Carp;
    my ($self, $age) = @_;
 
    if    (@_  > 2) {  confess "too many arguments" } 
 
    elsif (@_ == 1) {  return $struct->{'age'}      }
 
    elsif (@_ == 2) {
        carp "age `$age' isn't numeric"   if $age !~ /^\d+/;
 
        carp "age `$age' is unreasonable" if $age > 150;
        $self->{'age'} = $age;
 
    } 
} 
#-----------------------------
if ($^W) { 
 
    carp "age `$age' isn't numeric"   if $age !~ /^\d+/;
    carp "age `$age' is unreasonable" if $age > 150;
 
}
#-----------------------------
my $gripe = $^W ? \&carp : \&croak;
$gripe->("age `$age' isn't numeric")   if $age !~ /^\d+/;
 
$gripe->("age `$age' is unreasonable") if $age > 150;
#-----------------------------
struct Family => [head => 'Person', address => '$', members => '@'];  #'
 
#-----------------------------
struct Card => { 
    name    => '$',
    color   => '$',
    cost    => '$',
    type    => '$',
    release => '$',
    text    => '$',
 
};
#-----------------------------
struct Card => map { $_ => '$' } qw(name color cost type release text); #'
#-----------------------------
 
struct hostent => { reverse qw{
    $ name
    @ aliases
    $ addrtype
    $ length
    @ addr_list
 
}};
#-----------------------------
#define h_type h_addrtype
#define h_addr h_addr_list[0]
#-----------------------------
# make (hostent object)->
type()
 same as (hostent object)->
addrtype()
 
*hostent::type = \&hostent::addrtype;
 
# make (hostenv object)->
addr()
 same as (hostenv object)->addr_list(0)
 
sub hostent::addr { shift->addr_list(0,@_) }
 
#-----------------------------
package Extra::hostent;
use Net::hostent;
@ISA = qw(hostent);
 
sub addr { shift->addr_list(0,@_) } 
 
1;
#-----------------------------

[править] Cloning Objects

#-----------------------------
my $proto  = shift;
my $class  = ref($proto) || $proto;
 
my $parent = ref($proto) && $proto;
#-----------------------------
$ob1 = SomeClass->
 
new()
;
# later on
$ob2 = (ref $ob1)->
new();
 
#-----------------------------
$ob1 = Widget->new();
$ob2 = $ob1->new();
#-----------------------------
sub new {
    my $proto  = shift;
 
    my $class  = ref($proto) || $proto;
    my $parent = ref($proto) && $proto;
 
    my $self;
    # check whether we're shadowing a new from @ISA
    if (@ISA && $proto->SUPER::can('new') ) {
 
        $self = $proto->SUPER::new(@_);
    } else { 
        $self = {};
 
        bless ($self, $proto);
    }
    bless($self, $class);
 
    $self->{PARENT}  = $parent;
    $self->{START}   = time();   # init data fields
 
    $self->{AGE}     = 0;
    return $self;
} 
 
#-----------------------------

[править] Calling Methods Indirectly

#-----------------------------
$methname = "flicker";
$obj->$methname(10);         # calls $obj->flicker(10);
 
# call three methods on the object, by name
 
foreach $m ( qw(start run stop) ) {
    $obj->
 
$m();
 
} 
#-----------------------------
@methods = qw(name rank serno);
%his_info = map { $_ => $ob->$_() } @methods;
 
# same as this:
 
%his_info = (
    'name'  => $ob->
name()
 
,
    'rank'  => $ob->
rank()
,
    'serno' => $ob->
 
serno()
,
);
#-----------------------------
my $fnref = sub { $ob->method(@_) };
 
#-----------------------------
$fnref->(10, "fred");
#-----------------------------
$obj->method(10, "fred");
 
#-----------------------------
$obj->can('method_name')->($obj_target, @arguments)
   if $obj_target->isa( ref $obj );
 
#-----------------------------

[править] Determining Subclass Membership

#-----------------------------
$obj->isa("HTTP::Message");                  # as object method
HTTP::Response->isa("HTTP::Message");       # as class method
 
 
if ($obj->can("method_name")) { .... }       # check method validity
#-----------------------------
$has_io = $fd->isa("IO::Handle");
 
$itza_handle = IO::Socket->isa("IO::Handle");
#-----------------------------
$his_print_method = $obj->can('as_string');
#-----------------------------
Some_Module->VERSION(3.0);
 
$his_vers = $obj->
VERSION()
;
#-----------------------------
use Some_Module 3.0;
#-----------------------------
use vars qw($VERSION);
 
$VERSION = '1.01';
#-----------------------------

[править] Writing an Inheritable Class

#-----------------------------
package Person;
sub new {
 
    my $class = shift;
    my $self  = { };
 
    return bless $self, $class;
} 
sub name {
 
    my $self = shift;
    $self->{NAME} = shift if @_;
 
    return $self->{NAME};
} 
sub age {
 
    my $self = shift;
    $self->{AGE} = shift if @_;
 
    return $self->{AGE};
} 
#-----------------------------
use Person;
 
my $dude = Person->
new()
;
$dude->name("Jason");
 
$dude->age(23);
printf "%s is age %d.\n", $dude->name, $dude->age;
 
#-----------------------------
package Employee;
use Person;
@ISA = ("Person");
 
1;
#-----------------------------
use Employee;
my $empl = Employee->
new()
 
;
$empl->name("Jason");
$empl->age(23);
printf "%s is age %d.\n", $empl->name, $empl->age;
 
#-----------------------------
$him = Person::
new()
;               # WRONG
#-----------------------------

[править] Accessing Overridden Methods

#-----------------------------
sub meth { 
    my $self = shift;
 
    $self->SUPER::
meth()
;
}
#-----------------------------
$self->
meth();
                # Call wherever first meth is found
 
$self->Where::
meth();
         # Start looking in package "Where"
$self->SUPER::
meth(); 
        # Call overridden version
#-----------------------------
sub new {
 
    my $classname  = shift;         # What class are we constructing?
    my $self       = $classname->SUPER::new(@_);
 
    $self->_init(@_);
    return $self;                   # And give it back
} 
 
sub _init {
    my $self = shift;
    $self->{START}   = time();   # init data fields
 
    $self->{AGE}     = 0;
    $self->{EXTRA}   = { @_ };   # anything extra
 
}
#-----------------------------
$obj = Widget->new( haircolor => red, freckles => 121 );
 
#-----------------------------
my $self = bless {}, $class;
for my $class (@ISA) {
 
    my $meth = $class . "::_init";
    $self->$meth(@_) if $class->can("_init");
 
} 
#-----------------------------

[править] Generating Attribute Methods Using AUTOLOAD

#-----------------------------
package Person;
use strict;
use Carp;
 
use vars qw($AUTOLOAD %ok_field);
 
# Authorize four attribute fields
for my $attr ( qw(name age peers parent) ) { $ok_field{$attr}++; } 
 
sub AUTOLOAD {
    my $self = shift;
    my $attr = $AUTOLOAD;
 
    $attr =~ s/.*:://;
    return unless $attr =~ /[^A-Z]/;  # skip DESTROY and all-cap methods
 
    croak "invalid attribute method: ->
$attr()"
 unless $ok_field{$attr};
    $self->{uc $attr} = shift if @_;
 
    return $self->{uc $attr};
}
sub new {
 
    my $proto  = shift;
    my $class  = ref($proto) || $proto;
 
    my $parent = ref($proto) && $proto;
    my $self = {};
 
    bless($self, $class);
    $self->parent($parent);
    return $self;
 
} 
1;
#-----------------------------
use Person;
my ($dad, $kid);
 
$dad = Person->new;
$dad->name("Jason");
$dad->age(23);
$kid = $dad->new;
 
$kid->name("Rachel");
$kid->age(2);
printf "Kid's parent is %s\n", $kid->parent->name;
 
#Kid's parent is Jason
#-----------------------------
sub AUTOLOAD {
    my $self = shift;
    my $attr = $AUTOLOAD;
 
    $attr =~ s/.*:://;
    return if $attr eq 'DESTROY';   
 
    if ($ok_field{$attr}) {
        $self->{uc $attr} = shift if @_;
 
        return $self->{uc $attr};
    } else {
 
        my $superior = "SUPER::$attr";
        $self->$superior(@_);
    } 
 
}
#-----------------------------

[править] Solving the Data Inheritance Problem

#-----------------------------
sub Employee::age {
    my $self = shift;
 
    $self->{Employee_age} = shift if @_;
    return $self->{Employee_age};
 
} 
#-----------------------------
package Person;
use Class::Attributes;  # see explanation below
mkattr qw(name age peers parent);
 
#-----------------------------
package Employee;
@ISA = qw(Person);
use Class::Attributes;
 
mkattr qw(salary age boss);
#-----------------------------
package Class::Attributes;
use strict;
 
use Carp;
use Exporter ();
use vars qw(@ISA @EXPORT);
 
@ISA = qw(Exporter);
@EXPORT = qw(mkattr);
sub mkattr {
 
    my $hispack = caller();
    for my $attr (@_) {
 
        my($field, $method);
        $method = "${hispack}::$attr";
        ($field = $method) =~ s/:/_/g; 
 
        no strict 'refs'; # here comes the kluglich bit
        *$method = sub {
            my $self = shift;
 
            confess "too many arguments" if @_ > 1;
            $self->{$field} = shift if @_;
 
            return $self->{$field};   
        };
    } 
} 
 
1;
#-----------------------------

[править] Coping with Circular Data Structures

#-----------------------------
$node->{NEXT} = $node;
#-----------------------------
package Ring;
 
# return an empty ring structure
sub new {
    my $class = shift;
 
    my $node  = { };
    $node->{NEXT} = $node->{PREV} = $node;
 
    my $self  = { DUMMY => $node, COUNT => 0 };
 
    bless $self, $class;
    return $self;
}
#-----------------------------
use Ring;
 
$COUNT = 1000;
for (1 .. 20) { 
 
    my $r = Ring->
new()
;
    for ($i = 0; $i < $COUNT; $i++) { $r->insert($i) } 
 
}
#-----------------------------
# when a Ring is destroyed, destroy the ring structure it contains 
sub DESTROY {
    my $ring = shift;
 
    my $node;
    for ( $node  =  $ring->{DUMMY}->{NEXT};
 
          $node !=  $ring->{DUMMY}; 
          $node  =  $node->{NEXT} )
    {
 
             $ring->delete_node($node);
    } 
    $node->{PREV} = $node->{NEXT} = undef;
 
} 
 
# delete a node from the ring structure
sub delete_node {
    my ($ring, $node) = @_;
 
    $node->{PREV}->{NEXT} = $node->{NEXT};
    $node->{NEXT}->{PREV} = $node->{PREV};
 
    --$ring->{COUNT};
} 
#-----------------------------
# $node = $ring->search( $value ) : find $value in the ring
# structure in $node
sub search {
 
    my ($ring, $value) = @_;
    my $node = $ring->{DUMMY}->{NEXT};
 
    while ($node != $ring->{DUMMY} && $node->{VALUE} != $value) {
 
          $node = $node->{NEXT};
    }
    return $node;
} 
 
# $ring->insert( $value ) : insert $value into the ring structure
sub insert {
    my ($ring, $value) = @_;
 
    my $node = { VALUE => $value };
    $node->{NEXT} = $ring->{DUMMY}->{NEXT};
 
    $ring->{DUMMY}->{NEXT}->{PREV} = $node;
 
    $ring->{DUMMY}->{NEXT} = $node;
    $node->{PREV} = $ring->{DUMMY};
 
    ++$ring->{COUNT};
} 
 
# $ring->delete_value( $value ) : delete a node from the ring
# structure by value
sub delete_value {
 
    my ($ring, $value) = @_;
    my $node = $ring->search($value);
 
    return if $node == $ring->{DUMMY};
    $ring->delete_node($node);
 
}
 
 
1;
#-----------------------------

[править] Overloading Operators

#-----------------------------
use overload ('<=>' => \&threeway_compare);
 
sub threeway_compare {
    my ($s1, $s2) = @_;
 
    return uc($s1->{NAME}) cmp uc($s2->{NAME});
 
} 
 
use overload ( '""'  => \&stringify );
 
sub stringify {
    my $self = shift;
    return sprintf "%s (%05d)", 
 
            ucfirst(lc($self->{NAME})),
            $self->{IDNUM};
}
#-----------------------------
 
package TimeNumber;
use overload '+' => \&my_plus,
             '-' => \&my_minus,
             '*' => \&my_star,
             '/' => \&my_slash;
 
#-----------------------------
sub my_plus {
    my($left, $right) = @_;
 
    my $answer = $left->
new();
 
    $answer->{SECONDS} = $left->{SECONDS} + $right->{SECONDS};
 
    $answer->{MINUTES} = $left->{MINUTES} + $right->{MINUTES};
    $answer->{HOURS}   = $left->{HOURS}   + $right->{HOURS};
 
    if ($answer->{SECONDS} >= 60) {
        $answer->{SECONDS} %= 60;
 
        $answer->{MINUTES} ++;
    } 
 
    if ($answer->{MINUTES} >= 60) {
 
        $answer->{MINUTES} %= 60;
        $answer->{HOURS}   ++;
 
    } 
 
    return $answer;
 
} 
#-----------------------------
#!/usr/bin/perl
# show_strnum - demo operator overloading
use StrNum;           
 
 
$x = StrNum("Red"); $y = StrNum("Black");
$z = $x + $y; $r = $z * 3;
 
print "values are $x, $y, $z, and $r\n";
print "$x is ", $x < $y ? "LT" : "GE", " $y\n";
 
# values are Red, Black, RedBlack, and RedBlackRedBlackRedBlack
# Red is GE Black
#-----------------------------
# download the following standalone program
package StrNum;
 
use Exporter ();
@ISA = 'Exporter';
 
@EXPORT = qw(StrNum);  # unusual
 
use overload        (
        '<=>'   => \&spaceship,
        'cmp'   => \&spaceship,
        '""'    => \&stringify,
        'bool'  => \&boolify,
        '0+'    => \&numify,
        '+'     => \&concat,
        '*'     => \&repeat,
 
);
 
# constructor
sub StrNum($) { 
    my ($value) = @_; 
 
    return bless \$value; 
} 
 
sub stringify { ${ $_[0] } } 
 
sub numify    { ${ $_[0] } } 
 
sub boolify   { ${ $_[0] } } 
 
# providing <=> gives us <, ==, etc. for free.
sub spaceship { 
    my ($s1, $s2, $inverted) = @_;
 
    return $inverted ? $$s2 cmp $$s1 : $$s1 cmp $$s2;
} 
 
# this uses stringify
 
sub concat { 
    my ($s1, $s2, $inverted) = @_;
 
    return StrNum $inverted ? ($s2 . $s1) : ($s1 . $s2);
} 
 
# this uses stringify
sub repeat { 
    my ($s1, $s2, $inverted) = @_;
 
    return StrNum $inverted ? ($s2 x $s1) : ($s1 x $s2);
 
}
 
1;
 
#-----------------------------
#!/usr/bin/perl
# demo_fixnum - show operator overloading
use FixNum;
 
FixNum->places(5);
 
$x = FixNum->new(40);
$y = FixNum->new(12);
 
print "sum of $x and $y is ", $x + $y, "\n";
 
print "product of $x and $y is ", $x * $y, "\n";
 
$z = $x / $y;
printf "$z has %d places\n", $z->places;
 
$z->places(2) unless $z->places;
print "div of $x by $y is $z\n";
 
print "square of that is ", $z * $z, "\n";
 
sum of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 52
 
product of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 480
 
STRFixNum: 3 has 0 places
 
div of STRFixNum: 40 by STRFixNum: 12 is STRFixNum: 3.33
 
square of that is STRFixNum: 11.11
#-----------------------------
# download the following standalone program
package FixNum;
 
use strict;
 
my $PLACES = 0;
 
sub new {
    my $proto   = shift;
 
    my $class   = ref($proto) || $proto;
    my $parent  = ref($proto) && $proto;
 
    my $v = shift;
    my $self = {
 
        VALUE  => $v,
        PLACES => undef,
    }; 
    if ($parent && defined $parent->{PLACES}) {
 
        $self->{PLACES} = $parent->{PLACES};
    } elsif ($v =~ /(\.\d*)/) {
 
        $self->{PLACES} = length($1) - 1;
 
    }  else {
        $self->{PLACES} = 0;
 
    } 
    return bless $self, $class;
} 
 
sub places {
 
    my $proto = shift;
    my $self  = ref($proto) && $proto;
 
    my $type  = ref($proto) || $proto;
 
    if (@_) {
 
        my $places = shift;
        ($self ? $self->{PLACES} : $PLACES) = $places;
 
    } 
    return $self ? $self->{PLACES} : $PLACES;
} 
 
sub _max { $_[0] > $_[1] ? $_[0] : $_[1] } 
 
use overload '+'    => \&add,
             '*'    => \&multiply,
             '/'    => \&divide,
             '<=>'  => \&spaceship,
             '""'   => \&as_string,
             '0+'   => \&as_number;
 
sub add {
    my ($this, $that, $flipped) = @_;
 
 
    my $result = $this->new( $this->{VALUE} + $that->{VALUE} );
 
    $result->places( _max($this->{PLACES}, $that->{PLACES} ));
 
    return $result;
} 
 
sub multiply {
    my ($this, $that, $flipped) = @_;
 
    my $result = $this->new( $this->{VALUE} * $that->{VALUE} );
 
    $result->places( _max($this->{PLACES}, $that->{PLACES} ));
 
    return $result;
} 
 
sub divide {
    my ($this, $that, $flipped) = @_;
 
    my $result = $this->new( $this->{VALUE} / $that->{VALUE} );
 
    $result->places( _max($this->{PLACES}, $that->{PLACES} ));
 
    return $result;
} 
 
sub as_string {
    my $self = shift;
 
    return sprintf("STR%s: %.*f", ref($self), 
 
        defined($self->{PLACES}) ? $self->{PLACES} : $PLACES, 
            $self->{VALUE});
 
} 
 
sub as_number {
    my $self = shift;
 
    return $self->{VALUE};
} 
 
sub spaceship {
 
    my ($this, $that, $flipped) = @_;
    $this->{VALUE} <=> $that->{VALUE};
 
} 
 
1;
 
#-----------------------------

[править] Creating Magic Variables with tie

#-----------------------------
tie $s, "SomeClass"
#-----------------------------
 
SomeClass->
TIESCALAR()
#-----------------------------
$p = $s
#-----------------------------
$p = $obj->
FETCH()
#-----------------------------
$s = 10
 
#-----------------------------
$obj->STORE(10)
#-----------------------------
#!/usr/bin/perl
# demo_valuering - show tie class
use ValueRing;
tie $color, 'ValueRing', qw(red blue);
 
print "$color $color $color $color $color $color\n";
red blue red blue red blue
 
 
$color = 'green';
print "$color $color $color $color $color $color\n";
green red blue green red blue
#-----------------------------
# download the following standalone program
 
package ValueRing;
 
# this is the constructor for scalar ties
sub TIESCALAR {
    my ($class, @values) = @_;
 
    bless  \@values, $class;
    return \@values;
} 
 
# this intercepts read accesses
sub FETCH {
    my $self = shift;
 
    push(@$self, shift(@$self));
    return $self->[-1];
 
} 
 
# this intercepts write accesses
sub STORE {
    my ($self, $value) = @_;
 
    unshift @$self, $value;
    return $value;
} 
 
1;
 
#-----------------------------
no UnderScore;
#-----------------------------
#!/usr/bin/perl
# nounder_demo - show how to ban $_ from your program
no UnderScore;
@tests = (
 
    "Assignment"  => sub { $_ = "Bad" },
    "Reading"     => sub { print }, 
 
    "Matching"    => sub { $x = /badness/ },
    "Chop"        => sub { chop },
    "Filetest"    => sub { -x }, 
 
    "Nesting"     => sub { for (1..3) { print } },
 
);
 
while ( ($name, $code) = splice(@tests, 0, 2) ) {
 
    print "Testing $name: ";
    eval { &$code };
    print $@ ? "detected" : "missed!";
 
    print "\n";
} 
#-----------------------------
Testing Assignment: detected
 
Testing Reading: detected
 
Testing Matching: detected
 
Testing Chop: detected
 
Testing Filetest: detected
 
Testing Nesting: 123missed!
#-----------------------------
# download the following standalone program
package UnderScore;
 
use Carp;
sub TIESCALAR {
    my $class = shift;
 
    my $dummy;
    return bless \$dummy => $class;
} 
 
sub FETCH { croak "Read access to \$_ forbidden"  } 
sub STORE { croak "Write access to \$_ forbidden" } 
 
sub unimport { tie($_, _     _PACKAGE_     _) }
sub import { untie $_ } 
 
tie($_, _     _PACKAGE_     _) unless tied $_;
1;
 
#-----------------------------
#!/usr/bin/perl 
 
# appendhash_demo - show magic hash that autoappends
use Tie::AppendHash;
tie %tab, 'Tie::AppendHash';
 
$tab{beer} = "guinness";
 
$tab{food} = "potatoes";
$tab{food} = "peas";
 
while (my($k, $v) = each %tab) {
 
    print "$k => [@$v]\n";
}
#-----------------------------
food => [potatoes peas]
 
beer => [guinness]
#-----------------------------
# download the following standalone program
package Tie::AppendHash;
use strict;
 
use Tie::Hash;
use Carp;
use vars qw(@ISA);
 
@ISA = qw(Tie::StdHash);
sub STORE {
    my ($self, $key, $value) = @_;
 
    push @{$self->{key}}, $value;
} 
1;
 
#-----------------------------
#!/usr/bin/perl 
# folded_demo - demo hash that magically folds case
use Tie::Folded;
tie %tab, 'Tie::Folded';
 
$tab{VILLAIN}  = "big "; 
$tab{herOine}  = "red riding hood";
 
$tab{villain} .= "bad wolf";   
 
while ( my($k, $v) = each %tab ) {
 
    print "$k is $v\n";
}
#-----------------------------
heroine is red riding hood
 
villain is big bad wolf
#-----------------------------
 
# download the following standalone program
package Tie::Folded;
 
use strict;
use Tie::Hash;
use vars qw(@ISA);
 
@ISA = qw(Tie::StdHash);
sub STORE {
    my ($self, $key, $value) = @_;
 
    return $self->{lc $key} = $value;
    } 
 
sub FETCH {
    my ($self, $key) = @_;
 
    return $self->{lc $key};
} 
sub EXISTS {
 
    my ($self, $key) = @_;
    return exists $self->{lc $key};
 
} 
sub DEFINED {
    my ($self, $key) = @_;
 
    return defined $self->{lc $key};
} 
1;
 
#-----------------------------
#!/usr/bin/perl -w
# revhash_demo - show hash that permits key *or* value lookups
use strict;
use Tie::RevHash;
my %tab;
 
tie %tab, 'Tie::RevHash';
%tab = qw{
    Red         Rojo
    Blue        Azul
    Green       Verde
};
 
$tab{EVIL} = [ "No way!", "Way!!" ];
 
while ( my($k, $v) = each %tab ) {
 
    print ref($k) ? "[@$k]" : $k, " => ",
        ref($v) ? "[@$v]" : $v, "\n";
 
} 
#-----------------------------
[No way! Way!!] => EVIL
 
EVIL => [No way! Way!!]
 
Blue => Azul
 
Green => Verde
 
Rojo => Red
 
Red => Rojo
 
Azul => Blue
 
Verde => Green
#-----------------------------
# download the following standalone program
package Tie::RevHash;
 
use Tie::RefHash;
use vars qw(@ISA);
@ISA = qw(Tie::RefHash);
 
sub STORE {
    my ($self, $key, $value) = @_;
 
    $self->SUPER::STORE($key, $value);
    $self->SUPER::STORE($value, $key);
}
 
sub DELETE {
    my ($self, $key) = @_;
 
    my $value = $self->SUPER::FETCH($key);
    $self->SUPER::DELETE($key);
 
    $self->SUPER::DELETE($value);
}
 
1;
 
#-----------------------------
use Counter;
 
tie *CH, 'Counter';
while (<CH>) {
    print "Got $_\n";
 
} 
#-----------------------------
# download the following standalone program
package Counter;
sub TIEHANDLE {
 
    my $class = shift;
    my $start = shift;
 
    return bless \$start => $class;
} 
sub READLINE {
 
    my $self = shift;
    return ++$$self;
} 
 
1;
 
#-----------------------------
use Tie::Tee;
tie *TEE, 'Tie::Tee', *STDOUT, *STDERR;
 
print TEE "This line goes both places.\n";
#-----------------------------
#!/usr/bin/perl
# demo_tietee
use Tie::Tee;
use Symbol;
 
@handles = (*STDOUT);
for $i ( 1 .. 10 ) {
 
    push(@handles, $handle = gensym());
    open($handle, ">/tmp/teetest.$i");
} 
 
tie *TEE, 'Tie::Tee', @handles;
print TEE "This lines goes many places.\n";
#-----------------------------
# download the following standalone program
 
package Tie::Tee;
 
sub TIEHANDLE {
    my $class   = shift;
 
    my $handles = [@_];
 
    bless $handles, $class;
 
    return $handles;
}
 
sub PRINT {
    my $href = shift;
 
    my $handle;
    my $success = 0;
 
    foreach $handle (@$href) {
 
        $success += print $handle @_;
    }
 
    return $success == @$href;
 
}                                     
 
1;
 
#-----------------------------