Unix Review Column 13 -- Введение в объекты
Randal Schwartz
Март 1997Перевод 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; }
Здесь делается много действий. Вот их построчное описание.
- Подпрограмма в пакете Car с именем ``new''. Нет ничего особенного в имени ``new'', но это позволяет пользователям C++ и Smalltalk работать более комфортно.
- Первым параметром, передаваемым в подпрограмму является имя
класса (имя пакета). Эта подпрограмма сохраняет параметр в
локальной переменной с именем
$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; }
Также, здесь происходит несколько действий. Давайте разберем их по отдельности:
- Как и в случае вызова метода класса ``new'', также существует
дополнительный параметр. В данном случае -- это объект (например
$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аслаждайтесь!
Next Previous Contents