Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>
В моем предыдущем выпуске я начал обзор Объектно-ориентированных возможностей Perl. В качестве иллюстрации я создал класс, названный ``Car'', и дал этому классу ``методы'' позволяющие пассажирам входить, выходить и учитываться. Хотя вы можете читать этот выпуск, не прочитав предыдущий выпуск, но я вам настоятельно рекомендую сначала прочитать прочитать его. (Если у вас есть доступ к internet, то вы можете прочитать предыдущий выпуск по адресу http://www.stonehenge.com/merlyn/UnixReview/
Так, что продолжая с того, места там где я вас оставил в прошлый раз, давайте возьмем набор транспортных средств:
my $myCar = new Car; my $myTruck = new Truck;
Теперь, это должно создать новые объекты Car и Truck. Теперь, эти транспортные средства будут похожи во многих свойствах, но будут отличаться в других свойствах. Мы представим это, используя ``наследование'' от общего класса ``Vehicle'', например вот так:
BEGIN { @Car::ISA = "Vehicle"; @Truck::ISA = "Vehicle"; }
Причиной того, что я поместил этот код в блок BEGIN
, поскольку
необходимо, чтобы этот код был выполнен до того как я вызову
оператор new
, даже если, я помещу эти операторы в конце
программы. Обычно, все настройки @ISA
должны происходить до
выполнения первых ``настоящих'' строк кода.
Но что здесь делается? Как вы видите, нет метода
Car::new
. Вместо этого Perl будет искать этот метод в классе
Vehicle. Аналогично, нет метода Truck::new
. Вместо этого, у нас
имеется метод &Vehicle::new:
sub Vehicle::new { bless my $self = {}, shift; my %parms = @_; $self->{Passengers} = $parms{Passengers} || {}; $self; }
Заметьте, что это код очень похож на код Car::new
из
предыдущего выпуска. Первая строка создает анонимный ассоциативный массив,
присваивая его переменной $self
, и ассоциируя его с правильным
пакетом. Заметьте, что имя пакета получается как первый параметр, в этом
случае получаемом с помощью оператора shift
. Вторая строка создает
именованный ассоциативный массив %parms
из оставшихся аргументов
данного метода.
Третья строка отличается от строки метода Car::new
. Я позволяю
передавать список пассажиров, которые в начале будут посажены в машину,
например вот так:
$myCar = new Car Passengers => qw(Fred Dino);
Если такой список не был указан, то машина начинает свое движение пустой. Последняя строка возвращает созданную машину.
Так, что этот метод может использоваться и для класса Car и для класса Truck, для создания соответственно Car и Truck. Однако существует различее между Car и Truck, так, что мы получим немного проблем из-за ничего. Давайте добавим дополнительное поведение к этим транспортным средствам, и затем начнем различать их.
Сначала, давайте снова добавим методы enter
, leave
и
jump
из предыдущего выпуска:
$myCar->enter(qw(Fred Barney)); $myTruck->enter(qw(Wilma Betty)); $myTruck->jump(To => $myCar, People => qw(Wilma)); $myCar->leave(qw(Fred Barney Wilma));
Обычно говоря, Fred и Barney едут в машине, а Wilma и Betty трясутся в грузовике. Внезапно Wilma покидает грузовик и пересаживается в машину, а затем уговаривает Fred и Barney вместе с ней покинуть машину. (Betty все еще остается одна в грузовике). Давайте взглянем на то, как это будет реализовываться:
sub Vehicle::enter { my $self = shift; for (@_) { $self->{Passengers}->{$_} = "seat"; } $self; }
В этом коде, объект сохраняется в переменной $self
, а затем для
каждого из оставшихся аргументов, мы добавляем объект в хеш
Passengers
с именем в качестве ключа и номером места в качестве
значения (также как и в прошлом выпуске). Заметьте, что этот метод
определяется в классе Vehicle
, делая его доступным для классов Car
и Truck. Аналогичным образом определяется мето leave
:
sub Vehicle::leave { my $self = shift; for (@_) { delete $self->{Passengers}->{$_}; } $self; }
И в этом коде, отличие между методами enter
и leave
заключается в том, что они делают с хешем Passengers
. Метод
jump
является немного пугающим, но все равно достаточно легким для
чтения:
sub Vehicle::jump { my $self = shift; my %parms = @_; my $dest_vehicle = $parms{To} || new {ref $self}; my $people = $parms{People} || [keys %{$self->{Passengers}}]; $people = [$people] unless ref $people; $self->leave(@$people); $dest_vehicle->enter(@$people); $dest_vehicle; }
Первым делом, объект сохраняется в переменной $self
, а
оставшиеся аргументы формируют ассоциативный массив
%parms
. Выбирается транспортное средство назначения. Если
существует запись в ассоциативном массиве для ключа To
, то
выбирается это значение, в противном случае создается новое транспортное
средство.
Но какого типа должно быть транспортное средство? Произвольное
допущение, соединенное с годами программирования, позволяет мне считать,
что новое транспортное средство должно быть того же размера и формы, что и
средство, которое покидает пассажир. Мы можем получить тип вызывая ref
$self
, результат которого передается как ``объект'' для вызова
new
. (Было бы неплохо так создавать объекты в реальном мире? Но я
отклонился от темы...)
Далее следует назначенный пассажир, который переходит из машины в
машину. Это будет либо список, указанный параметром People
, либо
все пассажиры, находящиеся в машине. Заметьте, что здесь ожидается, что
$people
будет ссылкой на список, но если кто-то передаст скаляр,
то у нас позже будут проблемы, так что строка, которая начинается с
$people =
, если необходимо, превращает простое имя в
одно-элементный анонимный список.
После того, как определены исходное транспортное средство
($self
), люди (@$people
) и устройство в которое будет
производится переход ($dest_vehicle
) мы можем начать перемещение
людей. Сначала людей просят покинуть исходное транспортное средство, а
затем просят войти в требуемое транспортное средство. В заключение,
возвращается новое транспортное средство (которое может быть тем же самым,
что и переданное как параметр To
, или совершенно новым объектом
Car или Truck).
Хорошо, это реализует практически все, что необходимо для работы. Для проверки работы, мне необходимо иметь некоторый способ для доступа к текущему списку пассажиров:
sub Vehicle::passengers { my $self = shift; sort keys %{self->{Passengers}}; }
Почему бы не дать пользователям классов Car и Truck знать, что
$self->{Passengers}
является хешем, ключами которого являются
текущие пассажиры? Хорошо, я мог бы выбрать этот способ, если бы я был
ленив, или если бы производительность была бы критическим фактором. Однако,
я был привязан к этому интерфейсу навсегда, даже если бы позже я обнаружил,
что легче было бы список пассажиров реализовать в виде списка, а не
ассоциативного массива. Однако, лучше предоставить методы для доступа,
поддеpживающие pаботу пользователей не напpямую.
Эти классы транспортных средств имеют вопиюще малую обработку ошибок. Давайте добавим такую проверку, и покажем, почему классы Car и Truck являются различными, в одно и тоже время:
sub Vehicle::enter { my $self = shift; if (@_ + keys %{$self->{Passengers}} > $self->seats) { die "Cannot add @_ to $self"; } for (@_) { $self->{Passengers}->{$_} = "seat"; } $self; }
Давайте взглянем на то, что происходит здесь. Я добавляю длину списка
аргументов (в массиве @_
) к количеству ключей, для того, чтобы
посмотреть, не больше ли это число числа, возвращаемого при вызову метода
seats
. Если число больше, то в машину пытается сесть больше людей,
чем имеется мест, и происходит прекращение выполнения программы.
Но что такое метод seats
? Я его еще не определил, но он будет
выглядеть примерно так:
sub Vehicle::seats { 2 }
Это не слишком сложно. Теперь и грузовики и легковые машины, все имеют по 2 места, также как и все другие классы, унаследованный от Vehicle. Но подождите --- легковые машины имеют по 4 места! Так, что просто добавим этот факт:
sub Car::seats { 4 }
Когда вызывается метод $myCar->seats
, то он возвращает
значение 4 (поскольку мы нашли seats
прямо в Car
). Но
когда вызывается $myTruck->seats
, то метода
Truck::seats
не существует, так что Perl произведет поиск в путях
наследования, для того, чтобы найти значение по умолчанию для количества
мест в Vehicle
! Для отработки, мы могли бы определить еще
несколько типов:
BEGIN { @Motorcycle::ISA = "Vehicle"; @Van::ISA = "Vehicle"; @Unicycle::ISA = "Vehicle"; } sub Van::seats { 8 } sub Unicycle::seats { 1 }
И заметьте, что я не определил метод seats
для класса
Motorcycle, поскольку значение по умолчанию (2) является правильным.
То, что вы увидели здесь, является примером наследования и перегрузки
методов. Наследование выражается в том, что значение seats
для
2-местных транспортных средств получается прямо из объекта
Vehicle. Перезагрузка выражается в том, что seats
для транспортных
средств с другим количеством мест, перекрывает значение, определенное в
классе Vehicle.
Я надеюсь, что вы насладились этой краткой прогулкой в страну объектов, и вы смогли увидеть некоторые возможности. Наслаждайтесь!