
Объектно-ориентированное программирование в Perl 5
7 апреля 2011
Когда создавался Perl, ООП еще не был моден. Сейчас он моден, в связи с чем некоторые граждане, не обнаружив в этом языке любимых классов, испытывают культурный шок, плюются и идут учить модные PHP и Python. Тем не менее, если вы используете в своей работе Perl и хотите программировать в ООП стиле, язык не будет стоять у вас на пути. Единственная проблема, с которой вам предстоит столкнутся — это проблема выбора, потому что в Perl, как обычно, «there’s more than one way to do it».
Примечание: Для тех, кто не знает Perl, но хотел бы его освоить, в этом блоге есть соответствующая серия уроков.
1. Традиционные модули (еще не ооп)
Чуть более 99% скриптов на Perl — это небольшие утилиты в пару сотен строк кода. Но что делать в тех редких случаях, когда требуется написать большуюпрограмму, разбив ее код на несколько файлов? Традиционное решение, не имеющее отношения к ООП, выглядит следующим образом. Все функции выносятся в отдельные модули (файлы с расширением .pm). Основной же скрипт (файл .pl) подгружает эти модули, а те в свою очередь подгружают модули, от которых зависят сами. Скрипт берет функции, объявленные в подгруженных модулях, и с их помощью делает свое темное дело.
Рассмотрим пример модуля (файл MyLib.pm):
#!/usr/bin/perl
package MyLib;
use strict;
sub Test {
print "MyLib - Test Ok!\n";
}
1; # сообщает интерпретатору, что все ОК
… а также пример скрипта, использующего этот модуль:
#!/usr/bin/perl
use strict;
use MyLib;
MyLib::Test();
Теперь представим себе, что есть модуль, функции которого нам приходится использовать довольно часто. В этом случае некоторые функции было бы удобно импортировать в пространство имен скрипта. Перепишем пример соответствующим образом.
Файл MyLib.pm:
#!/usr/bin/perl
package MyLib;
use strict;
use Exporter 'import';
our @EXPORT_OK = qw/Test/;
sub Test {
print "MyLib - Test Ok!\n";
}
1;
Файл test.pl:
#!/usr/bin/perl
use strict;
use MyLib qw/Test/;
Test(); # вызовет MyLib::Test()
Подробности о модуле Exporter можно найти в CPAN’е.
2. Добавляем объектно-ориентированность
В Perl 5 есть три типа данных — скаляры, массивы и хэши. Как же получить с их помощью объекты? Оказывается, все очень просто. Берем специальную функцию bless(), передаем ей первым аргументом указатель на переменную (как правило, используются хэши) и название класса вторым аргументом. То, что было передано первым аргументом, становится объектом! Вот как это выглядит на практике.
Файл MyCalss.pm:
#!/usr/bin/perl
package MyClass; {
# ^ фигурные скобки - для красоты
# а в Perl >= 5.14 можно без точки с запятой
use strict;
sub new {
# получаем имя класса
my($class) = @_;
# создаем хэш, содержащий свойства объекта
my $self = {
name => 'MyClass',
version => '1.0',
};
# хэш превращается, превращается хэш...
bless $self, $class;
# ... в элегантный объект!
# эта строчка - просто для ясности кода
# bless и так возвращает свой первый аргумент
return $self;
}
# метод get_name();
sub get_name {
my($self) = @_; # ссылка на объект
return $self->{name}; # достаем имя из хэша
}
}
1; # ok!
Пример скрипта, использующего MyClass:
#!/usr/bin/perl
use strict;
use MyClass;
# создаем новый объект
# в конструктор можно было передать дополнительные аргументы
# которые шли бы в sub new() следом за именем класса
# my $cl = new MyClass(); # олдскульный стиль
my $cl = MyClass->new();
# доступ к имени можно получить напрямую
print "Name: ".$cl->{name}."\n";
# но правильнее делать это через гетер
# аналогично вызову MyClass::get_name($cl, другие-аргументы);
print "get_name() returns: ".$cl->get_name()."\n";
А вот — пример класса-наследника:
#!/usr/bin/perl
package MyClassChild; {
use base MyClass; # родительский класс
use strict;
# переопределяем конструктор
sub new {
my($class) = @_;
my $self = MyClass::new($class);
$self->{name} = "MyClassChild";
return $self;
}
# тут можно объявить дополнительные методы
}
1;
Тут все здорово и замечательно, но, к сожалению, нет инкапсуляции. Вернее, если я не ошибаюсь, это называется «инкапсуляция по соглашению». Это когда мы даем protected методам имена, начинающиеся, например, с одного подчеркивания, а private методам — имена, начинающиеся с двух подчеркиваний и говорим, что первые должны вызываться только из класса и его потомков, а вторые — только из класса. При желании можно даже написать небольшой скрипт, делающий проверку, что инкапсуляция нигде не нарушается. Я где-то слышал, что такой тип инкапсуляции вполне успешно используется в некоторых языках (SmallTalk ?).
Дополнение: Собственно, такой подход — это все, что вам нужно, если класс не имеет свойств (то есть вы просто экспортируете функции в ООП стиле). Если свойства есть, вы можете ограничить доступ к ним с помощью модулейAttribute::Constant, Scalar::Readonly и других.