среда, июля 02, 2014

Perl. Добавляем инкапсуляцию методам класса

Перл - замечательный язык с очень простой и понятной ООП-парадигмой. Из коробки мы имеем полиморфизм и наследование, но, к сожалению, не можем похвастаться инкапсуляцией.
Инкапсуляция, для тех кто не знаком с терминами, это сокрытие методов класса от посторонних лиц. Другими словами, мы в классе создаём различные методы, часть из которых доступна для вызова другими модулями/скриптами. Другая часть не должна быть вызвана нигде, кроме как в самом классе(private-метод) или в классе, который наследуется от данного класса(protected-метод).
В перле, как и в питоне, нельзя штатными средствами перекрыть доступ к методам класса. Применяется негласное соглашение, по которому методы(или функции), имена которых начинаются с нижнего подчёркивания, считаются приватными.

С этим в принципе можно мириться и ваять код на pure-перл, не прибегая к ООП-фреймворкам типа Moose, что я в принципе всегда и делал - меня слегка пугает перспектива тянуть в своё приложение 100500 модулей ради одного функционала.
К счастью, благодаря статье в журнале pragmaticperl я узнал о модуле Attribute::Protected. Данный модуль написал Tatsuhiko Miyagawa, очень известный разработчик в мире Perl.
Модуль достаточно легковесный и имеет в зависимостях только Attribute::Handlers, который давно является модулем ядра perl.
Рассмотрим на примере, как можно с помощью указанного модуля добавить в наш класс инкапсуляцию.
Для наглядности создадим 2 модуля и один скрипт, который будет вызывать оба модуля. Один модуль будет наследоваться от другого.

MyModuleParent.pm :
package MyModuleParent;

use strict;
use warnings;
use Attribute::Protected;

sub new {
    my $class = shift;

    return bless {}, $class;
}

sub _private_method : Private {
    print "This is private method in package " . __PACKAGE__ . "\n";
}

sub _protected_method : Protected {
    print "This is protected method in package " . __PACKAGE__ . "\n";
}

sub public_method {
    print "This is public method in package " . __PACKAGE__ . "\n";
}

1;

MyModule.pm :
package MyModule;

use strict;
use warnings;

use parent 'MyModuleParent';

sub mymethod {
    print "This is simple method in package " . __PACKAGE__ . "\n";
}

sub call_protect_method {
    my $self = shift;
    $self->_protected_method;
}

sub call_private_method {
    my $self = shift;
    $self->_private_method;
}

1;

script.pl :
#!/usr/bin/perl

use strict;
use warnings;

use MyModule;
use MyModuleParent;

my $parent_object = MyModuleParent->new;
my $object = MyModule->new;

$object->public_method;         # OK
$object->mymethod;              # OK
$object->call_protect_method;   # OK
$object->call_private_method;   # NOT OK

$parent_object->public_method;       # OK
$parent_object->_protected_method;   # NOT OK
$parent_object->_private_method;     # NOT OK
Объект "$object" может вызвать публичный метод класса "MyModuleParent", т.к. от него наследуется класс "MyModule". Также, данный объект вызывает методы "call_protect_method" и "call_private_method", вызывающие в свою очередь protect и private методы класса "MyModuleParent" соответственно. Вызов метода "call_private_method" завершится с ошибкой _private_method() is a private method of MyModuleParent!, вызов метода "call_protect_method" завершится успешно. Прямой вызов из скрипта private и protected методов класса "MyModuleParent"(через объект $parent_object) завершится с ошибкой.

Таким образом, мы получаем малой кровью(а оверхед там действительно ничтожный, проверенно в дебаггере) надёжную инкапсуляцию методов, очень похожую на сишную и явовску, да к тому же сделанную почти полностью на базовых средствах языка. Модулю Attribute::Protected уже 13 лет, удивитильно, что его так мало используют.

Комментариев нет: