Меняется Perl @ISA динамически? - PullRequest
2 голосов
/ 02 марта 2020

Я пытаюсь включить старую версию модуля динамически

В настоящее время у нас есть класс, который использует модуль ... очень упрощенный вопрос

use Modulev1;

our @ISA = qw( Modulev1 );

sub new {
    my $class = shift or return
    my $self = $class::SUPER::new( @_ ) or return;

    $self->do_stuff();

    $self;

}

У нас есть старый модуль, который имеет те же имена методов / переменные, но различную кодировку для другой версии API на удаленном сервере, которую я хотел бы подключить в качестве опции.

use Modulev1;
use Modulev2;

our @ISA = qw( Modulev1 );

sub new {
    my $class = shift or return;
    my %args = @_;

    if( $args{ use_version_2 } ) {
        @ISA = qw( Modulev2 );
    }

    my $self = $class::SUPER::new( \%args ) or return;

    $self->do_stuff();

    $self;

}

Это кажется работать одним способом (получает результаты), но это похоже на неправильный подход, и я получаю несколько предупреждений, таких как «модуль подпрограммы константы: переменная переопределена в /opt/perl/lib/5.30.1/Exporter.pm», что делает смысл в том, что он пытается определить эти константы дважды в каждой версии, и это кажется неправильным и склонным к будущим ошибкам.

Итак, есть 2 основных вопроса

1) Есть ли проблемы с переназначением @ISA , как у меня.

2) Есть ли лучший чистый способ попробовать и использовать 2 версии одного и того же модуля в коде, чем этот уродливый хак, подверженный ошибкам? Такое ощущение, что я должен как-то хранить модули более раздельно, чтобы они не могли пытаться делать что-то вроде переопределения констант.

Редактировать: я также попытался динамически добавить случай 'use module', но это кажется, что та же проблема, и кажется, что это будет неэффективно, так как это вызывается много раз.

Редактировать: просто чтобы прояснить, сценарий может вызывать объект несколько раз в течение одного запуска, но использовать разные версии.

Ответы [ 2 ]

3 голосов
/ 02 марта 2020

Помните, что @ISA - это переменная пакета (глобальная). Вы не можете изменить его для каждого экземпляра. Также помните, что наследование предназначено для объекта, который является более ограниченным понятием более общей идеи, а не способом отправки. Работа против этого наверняка принесет слезы.

Даже если бы вы могли локализовать его, вы могли бы когда-либо работать только с одним объектом за раз, потому что локализация переменной пакета на самом деле является динамической c. Изменение существует повсюду в программе, пока динамическая область действия c не очистится. Вам не нужны такие проблемы с синхронизацией.

Я думаю, что есть два более чистых способа справиться с этим.

  1. Дайте каждому модулю одинаковый (или закрытый) интерфейс и верните его объект напрямую. Ваш new тогда является «фабрикой», которая возвращает объект класса, соответствующего аргументам. Если вы можете иметь только один из них в программе в любое время, то вам следует подумать о том, как создать синглтон (или просто сделать все методом класса).

  2. Иметь Класс «адаптер» содержит объект нужного версионного модуля и переводит вызовы метода в версионный объект, который является частью экземпляра. Когда вы сталкиваетесь с методом, которого нет в версионном объекте, у вас есть способ жаловаться. Это полезно для сообщения об изменениях API и интерфейса в предупреждающих сообщениях.

3 голосов
/ 02 марта 2020

Основная проблема с изменением @ISA динамически, как это, заключается в том, что вы применяете это изменение ко всему классу, а не только к одному экземпляру, который был запрошен с параметром use_version_2. Это может не иметь значения, если ожидается, что объект будет использоваться в программе только один раз (т. Е. Это одиночный код), но это не очень хорошая практика. Другая незначительная проблема заключается в том, что всякий раз, когда @ISA изменяется, кэш метода сбрасывается (поскольку вызовы метода, вероятно, впоследствии разрешат разные подпрограммы).

Два альтернативных варианта, которые я бы рассмотрел, заключались в создании другого подкласс для каждой «версии» или для использования ролей с Role :: Tiny и с :: Roles , что позволяет динамически применять набор поведения к указанному c объекту по сути, создав подкласс на лету.

Например, вы можете сделать «Modulev1» и «Modulev2» в две роли и применить одну запрошенную, это также будет обрабатывать загрузку модуля.

# first role module
package Modulev1;
use Role::Tiny;
# subroutines to be composed in
1;

# second role module
package Modulev2;
use Role::Tiny;
# subroutines to be composed in
1;

# user of roles
use With::Roles;
...
if ($args{use_version_2}) {
  $self->with::roles('Modulev2');
} else {
  $self->with::roles('Modulev1');
}

Или вы можете создать два отдельных класса и поместить общий код двух версий в роль, которую они будут использовать статически, используя Role :: Tiny :: With (это похоже на то, как вы используйте «интерфейс» в некоторых других языках, за исключением того, что он также может предоставлять реализации).

package ModuleCommon;
use Role::Tiny;
requires 'foo', 'bar'; # require these functions to be implemented by versioned class
sub baz { }
1;

package Modulev1;
use Role::Tiny::With;
sub foo { }
sub bar { }
with 'ModuleCommon';
1;

Тогда вы сделаете определение, какое Класс ch для внешнего использования (как с функцией-оберткой), а не внутри одного из конструкторов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...