facebook LinkedIn
Google Book Search
Прегледи

Увод в програмирането на Perl Обекти и работа с тях

от Курс за ССОК

Увод в програмирането на Perl
Лекция #11 Обекти и модули

В обекториентираното програмиране всичко се разглежда като обект, което произлиза от теорията, че всичко около нас е обект и може да се опише чрез обектна терминология. ООП енкапсулира данни (атрибути) и функции (поведение) в класове, като данните и функциите на един клас са неразривно свързани. Класовете са като чертеж или схема, по която по-късно могат да се създадат обекти от този клас. Класовете имат свойството да скриват информация (information hiding), защото макар и комуникирайки помежду си чрез ясно дефинирани интерфейси, те не знаят как точно другите класове са имплементирани; детайлите са скрити в самите класове. ООП позволява създаването на интерфейси към данни, които интерфейси са независими от самата имплементация на данните. Ако имаме много програми, които ползват един обект чрез неговите методи, можем напълно да променим начина на имплементация на данните без да променяме програмите, които го ползват. Именно заради непрекъснатите промени (те са правило, а не изключение) е важно да се пише разбираем и лесен за поддръжка код. Класовете правят модификациите и преизползването на код (reuse) по-лесни. Как се ползва клас в Perl ще демонстрираме чрез модул FileHandle, който дефинира клас, предоставящ ООП интерфейс към filehandle. Модулът има няколко предимства пред обикновени filehandles като например възможността да се преминава между няколко filehandles без да се ползва функцията select или да ползва FileHandle обект навсякъде където се изисква filehandle. За да се ползва този или друг клас, първо трябва да се създаде инстанция на класа (обект на класа). Това става чрез конструктора на класа. Традиционно, конструктора се казва new, въпреки че Perl позволява името на конструктора да е различно, в зависимост от класа. Конструкторът връща обект на дадения клас. След конструкцията на обекта, методите му са достъпни със оператора стрелка (arrow operator):

objectName->method(arguments)

Пример:

#!/usr/bin/perl

use warnings;
use strict;
use FileHandle;
	 
my $write = new FileHandle;
my $read = new FileHandle;

$write->open(„>filehandle.txt“) or die(„Could not open write“);
$read->open(„input.txt“) or die(„Could not open read“);

$write->autoflush(1);

my $i = 1;

while(my $line = $read->getline()){
     $write->print($i++, „ $line“);
}

Интерфейс на един клас се състои от методите на класа и синтаксиса за тяхното повикване. Интерфейсът дефинира клиентския изглед към класа. Добре е интерфейсът и имплементацията да се отделят. Промени по имплементацията на един клас не афектират клиентския код докато интерфейсът си остава непроменен.

#!/usr/bin/perl

package Date;

use strict;
use warnings;

sub new {
     my $date = { the_year => 1000,
	        the_month => 1,
	        the_day => 1, };
     bless($date);
     return $date;
}

sub year {
     my $self = shift();
     $self->{the_year} = shift() if (@_);
     return $self->{the_year};
}

sub month {
     my $self = shift();
     $self->{the_month} = shift() if (@_);
     return $self->{the_month};
}

sub day {
     my $self = shift();
     $self->{the_day} = shift() if (@_);
     return $self->{the_day};
}

sub setDate {
     if(@_ == 4) {
          my $self = shift();
          $self->{the_month} = ($_[0]);
          $self->{the_day} = ($_[1]);
          $self->{the_year} = ($_[2]); 
     } else {
          print(„Method setDate requires three arguments.\n“);
     }     
}

sub print {
     my $self = shift();
     print($self->month);
     print(„/“);
     print($self->day);
     print(„/“);
     print($self->year);
     print(„/“);
}

return 1;

#!/usr/bin/perl

use Date;
use strict;
use warnings;

my $today = new Date;

$today->setDate(5, 14, 2006);
print($today->month());
print(„\n“);
$today->print();
print(„\n“);

В класа Date се ползва хеш, за да се запазват деня, месеца и годината за всяка дата. В конструктора се създава хеш препратка, която съдържа данните за обекта. За да се превърне тази препратка в Date обект, се ползва функцията bless, която приема препратка и я превръща в обект (в Perl всички обекти са препратки). Обектния тип е името на текущия пакет. Ако се подаде втори параметър на bless, то обектния тип ще е този втори параметър (примерно bless ($date, Date) ). Когато се вика метод, той работи като всяка друга функция, освен че първия елемент на @_ е обектът, за който методът е извикан. Т.е. извикването $birthDate->year() е аналогично на date::year($birthDate). Също така е важно всеки метод, който променя данните на един клас, да ги оставя в консистентно състояние (опитайте се да добавите валидираща логика за month() и day()). Често, едни класове ще наследяват други, които осигуряват атрибути и поведение нужни на новите класове. Налага се и в самите класове да се включват обекти на други класове като членове (композиция или агрегация). Подкласовете на един клас имат цялата негова функционалност плюс каквото допълнително е имплементирано в тях. Наследяването ни дава повече гъвкавост в преизползването на код, тъй като могат да се ползват предварително дефинирани класове за базови:

#!/usr/bin/perl
# Implementation of class Employee

package Employee;

use warnings;
use strict;
use Date;

sub new  {
     my $type = shift();
     my $class = ref($type) || $type;
     my $hireday = new Date;
     my $self = { firstName => undef,
	        lastName => undef,
	        hireDay => $hireDay };
     bless($self, $class);
     return $self;  
}

sub firstName {
     my $self = shift();
     $self->{firstName} = shift() if (@_);
     return $self->{firstName};
}

sub lastName {
     my $self = shift();
     $self->{lastName} = shift() if (@_);
     return $self->{lastName};
}     

sub hireDay {
     my $self = shift();
     if (@_) {
          $self->{hireDay}->setDate(@_);
     } else {
          $self->{hireDay}->print();
     }
}

return 1;

#!/usr/bin/perl
# Implementation of class Hourly

package Hourly;

use warnings;
use strict;
use Employee;
our ISA = („Employee“);

sub new  {
     my $object = shift();
     my $class = ref($object) || $object;

     my $self = $class->SUPER::new();
     $self->{rate} = undef;

     bless($self, $class);
     return $self;
}

sub rate {
     my $self = shift();
     $self->{rate} = shift() if (@_);
     return $self->{rate};
}

return 1;

#!/usr/bin/perl
# Using classes Employee and Hourly

use warnings;
use strict;
use Employee;
use Hourly;

my $worker = new Employee;

$worker->firstName(„Jason“);
$worker->lastName(„Black“);
$worker->hireDay(8, 5, 1995);
print( $worker->firstName(), „ “,
          $worker->lastName(), „ was hired on “);
$worker->hireDay();
print(„.\n\n“);

my $hour = new Hourly;

$hour->firstName(„John“);
$hour->lastName(„White“);
$hour->hireDay(11, 30, 1999);
$hour->rate(9.50);
print($hour->firstName(), „ “, $hour->lastName(), „ was hired on “ );
$hour->hireDay();
print(„.\n“);
printf(„He makes \$%.2f per hour.\n“, $hour->rate());

Единственото нещо, което трябва да се направи по-различно при имплементацията на базов клас е в конструктора му new. Причината е, че този конструктор може да бъде наследен от подкласове, които ще го ползват за създаване на обект от техния тип, а не от типа на базовия клас. Затова, първото нещо, което трябва да се направи, е да се определи типа на обекта, който се създава. Този тип е подаван като първия аргумент, и може бъде взет с shift. Има два начина да бъде извикан конструктор:

  1. Type->new() или new Type.
  2. $object->new() (като метод на обект, който има същия тип като създавания обект).

Затова ref($type) || $type инициализира $class или с типа на препратката или името на типа, което е подадено. Добра практика е винаги даден клас да се имплементира за наследяване, както и да се ползва двуаргументната форма на bless. За да се наследи class Employee, в @ISA трябва да се специфицира Employee, при което Perl автоматично инициализира $class::SUPER с Employee. Когато се извика метод, който не е дефиниран в текущия клас, Perl търси класовете в @ISA, за да локира клас, който има търсения метод. Кода $class->SUPER::new() явно извиква new метода на Employee. Методът new на клас Hourly ще изпрати тип Hourly на new метода на Employee. По този начин конструкторът на Employee ще "благослови"(bless) хеш препратката в обект от тип Hourly, а не Employee.

Локални линукс групи RSS
Дискусии