3. Inside-out классы
В предыдущем примере каждому объекту соответствовал свой хэш, благодаря чему пользователь класса мог получить доступ к любому его свойству. Идея «классов наизнанку» заключается в том, чтобы ограничить доступ к свойствам, используя один хэш для всех экземпляров класса.
Пример inside-out класса:
#!/usr/bin/perl
package MyClass; {
use strict;
use Scalar::Util qw/refaddr/;
# $items{refaddr $obj}{name} = value
my %items;
sub new {
my($class, %args) = @_;
# сам объект представляет собой пустой хэш
my $self = bless {}, $class;
my $id = refaddr $self;
$items{$id}{name} = $args{name} || 'Anonymous';
$items{$id}{version} = '1.0';
return $self;
}
sub get_name {
my($self) = @_;
return $items{refaddr $self}{name};
}
sub DESTROY {
my($self) = @_;
# print $items{refaddr $self}{name}." destroyed!\n";
delete $items{refaddr $self};
}
}
1; # ok!
Пример класса-наследника не привожу, потому что там все тривиально. Скрипт, использующий наш класс:
#!/usr/bin/perl
use strict;
use MyClass;
my $anonymous = MyClass->new();
my $alexandr = MyClass->new(
name => "Alexandr"
);
print "anonymous->get_name() = ".$anonymous->get_name()."\n";
print "alexandr->get_name() = ".$alexandr->get_name()."\n";
Программист, который будет использовать класс, не может получить доступ к хэшу %MyClass::items, потому что он объявлен в модуле MyClass как «my», а не «our». Поскольку все свойства класса хранятся в этом хеше, они автоматически становятся закрытыми. При желании мы даже можем объявить закрытые методы.
Однако у этого подхода есть МНОГО недостатков. Во-первых, мы не можем получить protected методы. Впрочем, некоторые программисты заверяют, что при правильном проектировании они не очень то и нужны. Во-вторых, чтобы избежать утечки памяти, мы должны явно освобождать ее в деструкторе. В-третьих, серьезные проблемы возникают в многопоточных приложениях. И, наконец, стандартные средства сериализации не работают с inside-out объектами.
По названным причинам inside-out классы редко объявляются описанным выше образом. Обычно для этого используются специальные CPAN-модули.
4. Модуль Class::InsideOut и иже с ним
Одним из таких модулей является Class::InsideOut. Пример класса, созданного с его помощью:
#!/usr/bin/perl
package MyClass; {
use strict;
use Class::InsideOut qw/:std/;
# для объявления public-свойств
# public ___ => my %____;
# свойство, открытое только для чтения
readonly name => my %name;
# закрытое свойство
private version => my %version;
sub new {
my($class, %args) = @_;
my $self = bless {}, $class;
register($self); # для многопоточности
$name{id $self} = $args{name} || 'Anonymous';
return $self;
}
}
1; # ok!
Пример дочернего класса:
#!/usr/bin/perl
package MyClassChild; {
use base MyClass;
use Class::InsideOut qw/:std/;
use strict;
# опять же, тут никаких protected членов
readonly second_name => my %second_name;
sub new {
my ($class, %args) = @_;
my $self = MyClass::new($class, %args);
$second_name{id $self} = $args{second_name} || 'Noname';
return $self;
}
sub private_test : PRIVATE {
# ...
}
}
1;
Пример скрипта, использующего MyClassChild:
#!/usr/bin/perl
use strict;
use MyClassChild;
use Scalar::Util qw/blessed/;
my $anonymous = MyClassChild->new();
my $alexandr = MyClassChild->new(
name => "Alexandr",
second_name => "Alexeev"
# blog => http://eax.me/
);
print "anonymous->name() = ".$anonymous->name()."\n";
print "alexandr->name() = ".$alexandr->name()."\n";
# пример RTTI в Perl
if(blessed($alexandr) eq "MyClassChild") {
print "alexandr->second_name() = ".
$alexandr->second_name()."\n";
}
Идея та же, что и в предыдущем пункте, только каждому свойству соответствуетотдельный хэш. На этот раз не нужно освобождать память в деструкторе или беспокоиться о многопоточности — Class::InsideOut сделает все за нас.
Из недостатков модуля хотелось бы отметить отсутствие «традиционных» сетеров/геттеров, имена которых начинались бы с set_ и get_, а также нерешенную проблему с сериализацией. В описании модуля сказано, что данный функционал находится в разработке. Учитывая, что модуль последний раз обновлялся в 2008 году, вряд ли мы этого функционала дождемся.
Среди аналогичных CPAN-модулей следует отметить Object::InsideOut иClass::Std. Первый отпугнул меня объемом документации, но выглядит неплохо. Второй поддерживает «правильные» сетеры и гетеры, а также сериализацию, но в нем не реализована поддержка многопоточности.
В целом классы наизнанку критикуют за отсутствие единого стандарта реализации, зависимость от модулей, излишнюю сложность и прочие недостатки.
