Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>
Объекты, объекты, объекты! Мир программирования становится ``объектно-ориентированным''. И Perl не является исключением -- в нем тоже имеются объекты, уже в течении последних 30% его жизни. Однако, в отличии от других языков программирования, вы можете писать код на Perl используя объекты, или полностью избегать их использования. Давайте взглянем на систему объектов в Perl.
В Perl ``объект'' это просто ссылка на структуру данных. (Если вы хотите обновить свои знания, то посмотрите как я использовал ссылки в нескольких предыдущих выпусках, или посмотрите документацию получаемую с помощью команды ``man perlref''). Однако, ссылки могут быть ``связаны'' с определенными пакетами, представляя данный пакет в виде ``класса'' объекта.
Назначение связывания состоит в том, чтобы разрешить вызов ``методов'' для данного объекта. Метод в этом случае -- простая подпрограмма, но но подпрограмма, находящаяся в пакете, с которым связывается объект.
Давайте взглянем на простой пример. Я хочу создать класс с именем Car (Машина). Внутри этого класса я помещаю метод, который создает новые машины, которые я могу использовать. Этот метод называется ``new''. Вызов метода выглядит примерно так:
$myCar = new Car;
Теперь, для того чтобы этот код работал, нам в пакете Car необходимо иметь подпрограмму с именем ``new''. Это будет выглядеть примерно так:
sub Car::new { my $class = shift; my $self = {}; bless $self, $class; $self->{passengers} = {}; $self; }
Здесь делается много действий. Вот их построчное описание.
$class
. Это одно из отличий
между вызовом метода и вызовом простой подпрограммы: всегда есть
дополнительный первый параметр.$self
. Этой переменной присваивается пустой анонимный
хеш. Это достаточно распространенная практика, поскольку переменные
класса (переменные: которые уникальны для каждого из объектов)
являются просто элементами этого хеша.$self
, но анонимный хеш ``запоминает'', что он пришел из
данного пакета. Эта ``память'' позволяет найти методы в нужном
пакете, при последующих вызовах.$self
создается переменная класса с именем
``passengers (пассажиры)'' в виде анонимного хеша (ссылки).$self
.Заметьте, что в этом случае возвращаемым значением является ссылка на
анонимный хеш, но также этот хеш был связан с пакетом Car. Вы могли бы
использовать переменную $myCar
(определенную ранее) просто как
ссылку на анонимный хеш. Однако, объекты лучше работают, в тех случаях,
когда мы рассматриваем их как ``черные ящики'', вместо того, чтобы
взаимодействовать с объектами через ``методы объектов (instance)''.
Давайте взглянем на методы работающие с данным классом. Давайте посадим ``Fred'' и ``Wilma'' в машину:
enter $myCar qw(Fred Wilma);
Это код отличается тем, что вместо имени класса после имени метода, у нас имеется имя объекта. Также заметьте, что у нас имеются параметр, переданный методу, который следует за именем объекта. Давайте взглянем на то, как это обрабатывается внутри подпрограммы:
sub Car::enter { my $self = shift; foreach $_ (@_) { $self->{passengers}->{$_} = "seat"; } $self; }
Также, здесь происходит несколько действий. Давайте разберем их по отдельности:
$myCar
). Первым действием подпрограммы является помещение этого
объекта в переменную $self
. Она будет той же самой переменной
$self
, как и было определено в подпрограмме ``new''.$self
будет удалено из списка
параметров, оставшиеся значения в массиве @_
будут являться
людьми, которые сейчас находятся в машине. Цикл foreach
устанавливает элементы внутри переменной объекта с именем ``passengers'' (на
которую ссылаются как на элемент в хеше $self
). Ключом хеша
будет имя пассажира, а соответствующим значением будет номер места,
показывающее, что пассажир имеет место (более поздняя версия данной
подпрограммы, возможно будет записывать выбор места, но сейчас просто будем
сохранять данное значение).$self
, но мы все равно возвращаем
$self
, по причинам, которые станут очевидными далее.
Так, что этот метод заставляет включить людей, перечисленных в качестве аргументов подпрограммы, в переменную ``passengers''.
Иногда, вызов лучше записывается используя стрелочную нотацию, которая выглядит примерно так:
$myCar = Car->new; $myCar->enter(qw(Fred Barney));
В действительности, поскольку возвращаемым значением метода
enter
$myCar = Car->new->enter(qw(Fred Barney));
Это является преимуществом стрелочной нотации. Мы также можем переписать этот код, используя другую нотацию (``косвенный объект'') notation as:
$myCar = enter {new Car} qw(Fred Barney);
но как вы можете заметить, параметры все больше и больше отдаляются от имени метода.
После того, как пассажиры были добавлены, нам нужно посмотреть кем они являются. Это обрабатывается с помощью дополнительного метода:
@passengers = passengers $myCar; print "passengers for $myCar is @passengers\n";
что приводит к выдаче следующего результата:
passengers for Car=HASH(0xb126c) is Barney Fred
Посмотрите на значение $myCar
, когда оно интерпретируется как
строка. В действительности, это просто отладочный символ, в форме:
Class=UNDERLYINGTYPE(0xHexAddress)
Также заметьте, что пользователю класса Car нет необходимости знать то, что список пассажиров хранится в хеше (ассоциативном списке)...если далее мы обнаружим, что эффективней будет использования двоичного дерева или сортированого списка, то мы сможем перейти к использованию этих алгоритмов, поскольку интерфейс останется тем же самым. Вот почему важно рассматривать объект как черный ящик.
Вот одно из возможных определений метода ``passengers'':
sub Car::passengers { my $self = shift; sort keys %{$self->{passengers}}; }
Здесь как и в предыдущих примерах первым параметром является сам объект
(например $myCar
). Это значение затем используется для доступа к
переменной ``passengers''. Затем для этого хеша вызывается оператор
keys()
, и результат функции сортируется в АЛФАВИТHОМ порядке.
Для полноты примера, давайте добавим еще один метод --- удаление добавленных пассажиров. Это будет выглядеть примерно следующим образом:
sub Car::leave { my $self = shift; for (@_) { delete $self->{passengers}->{$_}; } $self; }
Давайте теперь соединим все примеры вместе:
%cars = ( Flintstone => Car->new->enter(qw(Fred Wilma)), Rubble => Car->new->enter(qw(Barney Betty)), ); ## show who is where foreach $family (sort keys %cars) { my @passengers = $cars{$family}->passengers; print "the $family car contains @passengers\n"; }
Здесь мы используем два семейных автомобиля, хранящихся в хеше с именем
%cars
. Ключом хеша является фамилия семейства, а соответствующим
значением будет экземпляр класса Car. Затем следует простой код, который
выдает списки пассажиров в машинах.
Это позволяет нам выполнять типичные действия, такие как пересадка Wilma из одной машины в другую:
## wilma decides it is better to ride with betty $cars{Flintstone}->leave("Wilma"); $cars{Rubble}->enter("Wilma");
Давайте выполним это типичное действие в виде метода класса, и проиллюстрируем именованные параметры.
sub Car::jump { my $self = shift; my %parms = @_; my $dest_car = $parms{TO} || Car->new; my $people = $parms{PEOPLE} || [$self->passengers]; $people = [$people] unless ref $people; $self->leave(@$people); $dest_car->enter(@$people); }
Это позволит нам выполнять что-то подобное следующему коду:
## wilma goes to the rubble car $cars{Flintstone}->jump( PEOPLE => qw(Wilma), TO => $cars{Rubble}, ); ## but it breaks down, so they all go to the other car $cars{Rubble}->jump( TO => $cars{Flintstone}, ); ## and then that breaks down, so they all get in a new car $newCar = $cars{Flintstone}->jump; @passengers = $newCar->passengers; print "new car contains @passengers\n";
Попробуйте выполнить эти примеры. Они открывают большое поле для экспериментов.
Как вы увидели, объект позволяют нам определить новые типы данных (машина) и операции выполняемые над этими типами. В будущем выпуске я проиллюстрирую другие действия с объектами, такие как наследование. Hаслаждайтесь!