Я знаю, что Perl не является статически типизированным, когда я хочу применить этот механизм к Perl объекту производного класса:
Скажем, у меня есть базовый класс B
и производный класс D
наследуется от B
. Также у меня есть объект $obj
, который содержит D
объект. Функция Bf()
ожидает параметр типа B
.
Очевидно (по правилам полиморфизма) я могу передать $obj
в Bf()
как Bf($obj)
, но в отличие от состояния c -типированный язык Bf()
увидит весь объект D
(а не только элементы B
).
Есть ли (довольно чистое и простое) решение этой проблемы в Perl? Решение должно «скрывать» атрибуты (и методы), которые B
не имеет от D
в Bf()
, не ограничивая модификации оригинального B
(что на самом деле D
).
Только для взрослых программистов (добавлено 2020-03-06)
Хорошо, люди хотели более конкретного описания. К сожалению (как указывалось), исходная программа очень сложна и использует отражающие механизмы для автоматической генерации методов получения, установки и форматирования, поскольку я действительно не могу привести здесь минимальный рабочий пример, поскольку она не быть минимальным.
Сначала у меня есть класс MessageHandler
, который обрабатывает сообщения (не удивительно!). Тогда у меня есть функция log_message($$$)
, которая ожидает (среди прочего) объект MessageHandler
в качестве первого аргумента.
Тогда у меня есть эта иерархия классов (в реальности она намного сложнее):
MessageHandler
ControlMessageHandler (ISA: MessageHandler)
ControlMessageResponseHandler (ISA: ControlMessageHandler)
Теперь, если log_message
хочет MessageHandler
, я могу передать ControlMessageResponseHandler
, поскольку оно соответствует MessageHandler
. Но при этом открываются все атрибуты от ControlMessageResponseHandler
до log_message
, которых нет в MessageHandler
.
Опасность заключается в том, что log_message
может (по ошибке) получить доступ к атрибуту ControlMessageResponseHandler
этого нет в MessageHandler
. Чтобы предотвратить ошибки, я хотел бы предотвратить это или, по крайней мере, получить предупреждение (как если бы я получал статически типизированный язык как Eiffel).
Грязные детали внутри
На всякий случай это важно, я обрисую, как строятся мои объекты массива (для рабочего примера потребуется много дополнительного кода):
Сначала индексы массива распределяются автоматически следующим образом:
use constant I_VERBOSITY => IS_NEXT->(); # verbosity level
use constant I_TAG => IS_NEXT->(); # additional tag
use constant I_TAG_STACK => IS_NEXT->(); # tag stack
use constant I_MSG_DEBUG => IS_NEXT->(); # handler for debug messages
...
use constant I_LAST => IS_LAST->(); # last index (must be last)
I_LAST
необходим для наследования. Атрибуты определяются следующим образом:
use constant ATTRIBUTES => (
['verbosity', I_VERBOSITY, undef],
['tag', I_TAG, \&Class::_format_string],
['tag_stack', I_TAG_STACK, undef],
['msg_debug', I_MSG_DEBUG, \&Class::_format_code],
...
);
Определение содержит подсказку, как форматировать каждый атрибут. Эта информация используется для настройки средств форматирования для форматирования каждого атрибута следующим образом:
use constant FORMATTERS =>
(map { Class::_attribute_string($_->[0], $_->[1], undef, $_->[2]) }
ATTRIBUTES); # attribute formatters
Получатели и установщики автоматически определяются следующим образом:
BEGIN {
foreach (ATTRIBUTES) {
Class::_assign_gs_ai(__PACKAGE__, $_->[0], $_->[1]);
}
}
Конструктор будет использовать следующие строки:
my $self = [];
$#$self = I_LAST;
$self->[I_VERBOSITY] = $verbosity;
...
И, наконец, моя процедура печати объекта выглядит следующим образом:
sub as_string($)
{
my $self = shift;
my $a_sep = ', ';
return join($a_sep, map { $_->($self, $a_sep) } FORMATTERS);
}
С наследованием это выглядит так:
sub as_string($)
{
my $self = shift;
my $a_sep = ', ';
return join($a_sep, $self->SUPER::as_string(),
map { $_->($self, $a_sep) } FORMATTERS);
}