Next Previous Contents

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. Вы могли бы использовать переменную $myCar (определенную ранее) просто как ссылку на анонимный хеш. Однако, объекты лучше работают, в тех случаях, когда мы рассматриваем их как ``черные ящики'', вместо того, чтобы взаимодействовать с объектами через ``методы объектов (instance)''.

Давайте взглянем на методы работающие с данным классом. Давайте посадим ``Fred'' и ``Wilma'' в машину:


        enter $myCar qw(Fred Wilma);

Это код отличается тем, что вместо имени класса после имени метода, у нас имеется имя объекта. Также заметьте, что у нас имеются параметр, переданный методу, который следует за именем объекта. Давайте взглянем на то, как это обрабатывается внутри подпрограммы:


        sub Car::enter {
                my $self = shift;
                foreach $_ (@_) {
                        $self->{passengers}->{$_} = "seat";
                }
                $self;
        }

Также, здесь происходит несколько действий. Давайте разберем их по отдельности:

  1. Как и в случае вызова метода класса ``new'', также существует дополнительный параметр. В данном случае -- это объект (например $myCar). Первым действием подпрограммы является помещение этого объекта в переменную $self. Она будет той же самой переменной $self, как и было определено в подпрограмме ``new''.
  2. После того, как значение $self будет удалено из списка параметров, оставшиеся значения в массиве @_ будут являться людьми, которые сейчас находятся в машине. Цикл foreach устанавливает элементы внутри переменной объекта с именем ``passengers'' (на которую ссылаются как на элемент в хеше $self). Ключом хеша будет имя пассажира, а соответствующим значением будет номер места, показывающее, что пассажир имеет место (более поздняя версия данной подпрограммы, возможно будет записывать выбор места, но сейчас просто будем сохранять данное значение).
  3. Хотя значение возвращаемое данным методом, в действительности является побочным эффектом изменения $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