Какой самый чистый способ дублировать функциональность base / parent.pm для необъектных модулей perl? - PullRequest
6 голосов
/ 11 августа 2010

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

Короче, мне интересно, каксделать наследование модуля, как base.pm/parent.pm сделать это для объектно-ориентированных модулей;только для модулей на основе экспортера.

Гипотетический пример того, что я имею в виду:

Вот наш скрипт.Изначально он загружал Foo.pm и вызывал из него baz (), но baz () имеет ужасную ошибку (как мы скоро увидим), поэтому мы сейчас используем Local / Patched / Foo.pm, который должен исправить ошибку.Мы делаем это, потому что в этом гипотетическом случае мы не можем изменить Foo (это модуль cpan в активной разработке, вы видите), и он огромен (серьезно).

#!/usr/bin/perl

# use Foo qw( baz [... 100 more functions here ...] );
use Local::Patched::Foo qw( baz [... 100 more functions here ...] );
baz();

Вот Foo.pm,Как вы можете видеть, он экспортирует baz (), который вызывает qux, что приводит к ужасной ошибке, вызывающей сбой.Однако мы хотим сохранить baz и остальную часть Foo.pm без тонны копирования-вставки, тем более что они могут измениться позже из-за того, что Foo все еще находится в разработке.

package Foo;
use parent 'Exporter';
our @EXPORT = qw( baz [... 100 more functions here ...] );
sub baz { qux(); }
sub qux { print 1/0; }            # !!!!!!!!!!!!!
[... 100 more functions here ...]
1;

Наконец,поскольку Foo.pm используется во МНОГИХ местах, мы не хотим использовать Sub :: Exporter, поскольку это будет означать копирование вставки исправления bandaid во все эти многие места.Вместо этого мы пытаемся создать новый модуль, который действует и выглядит как Foo.pm, и действительно загружает 99% своей функциональности, все еще из Foo.pm и просто заменяет уродливый сабвуфер qux на лучший.

Далее следует, как такая вещь выглядела бы, если бы Foo.pm был объектно-ориентированным:

package Local::Patched::Foo;
use parent 'Foo';
sub qux { print 2; }
1;

Теперь это, очевидно, не будет работать в нашем текущем случае, так как parent.pm просто не делает этоговещь.

Существует ли простой и понятный способ записи Local / Patched / Foo.pm (с использованием любых применимых модулей CPAN) таким образом, чтобы это работало, если не считать копирования пространства имен функции Foo.pm вручную?

Ответы [ 6 ]

4 голосов
/ 11 августа 2010

Если это одна подпрограмма, которую вы хотите переопределить, вы можете сделать несколько обезьяньих исправлений:

*Foo::qux = \&fixed_qux;

Я не уверен, что это самое чистое или лучшее решение, новременная временная задержка до тех пор, пока апстрим не исправит ошибку в qux, это должно сделать.

3 голосов
/ 12 августа 2010

Просто добавив еще один способ к функции обезьяны-patch Foo qux, без какой-либо ручной манипуляции с typeglob.

package Local::Patched::Foo;
use Foo (); # load but import nothing

sub Foo::qux {
    print "good qux";
}

Это работает, потому что пакеты Perl всегда изменяемы, идо тех пор, пока приведенный выше код появляется после загрузки Foo.pm, он будет переопределять существующую подпрограмму baz.Вам также может понадобиться no warnings 'redefine';, чтобы заставить замолчать любые предупреждения.

Затем использовать его:

use Local::Patched::Foo;
use Foo qw( baz );

baz();  # calls the patched qux() routine

Вы можете покончить с двумя строками use, написав собственный метод импорта вLocal::Patched::Foo следующим образом:

# in the Local::Patched::Foo package:

sub import {
    return unless @_;             # return if no imports
    splice @_, 0, 1, 'Foo';       # change 'Local::Patched::Foo' to 'Foo'
    goto &{ Foo->can('import') }; # jump to Foo's import method
}

И тогда это просто:

use Local::Patched::Foo qw( baz );

baz();  # calls the patched qux()
1 голос
/ 12 августа 2010

Вместо того, чтобы угнать ответ Александра (который был правильным, но неполным), вот решение под отдельной копией:


package Foo;
use Exporter 'import';
our @EXPORT = qw(foo bar baz qux);
our %EXPORT_TAGS = (
    'all' => [ qw(foo bar baz qux) ],
    'all_without_qux' => [ qw(foo bar baz) ],
);

sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
sub qux { 'qux' }

1;

package Foo::Patched;
use Foo qw(:all_without_qux);
use Exporter 'import';
our @EXPORT = qw( foo bar baz qux );

sub qux { 'patched qux' }

1;

package main;
use Foo::Patched;

print qux();

Вы также можете use Foo; в своей программе , если вы используете его до Foo::Patched, или вы перезапишете пропатченный qux оригинальной неработающей версией.

Здесь есть несколько нравов (по крайней мере, они ИМХО):

  1. не экспортируйте в пространство имен вызывающего без явного указания (т. Е. Оставьте @EXPORT пустым и используйте @EXPORT_OK и %EXPORT_TAGS, чтобы разрешить вызывающему указывать именно то, что ему нужно. экспортировать вообще и использовать полные имена для всех функций библиотеки.
  2. Напишите свои библиотеки так, чтобы функции назывались в стиле OO: Foo->function вместо Foo::function. Это значительно упрощает переопределение функции с помощью стандартного синтаксиса use base, который мы все знаем и любим, без необходимости возиться с таблицами символов обезьяны или манипулировать списками экспортеров.
1 голос
/ 12 августа 2010

Один из подходов заключается в простой замене дополнительной ссылки.если вы можете установить его, используйте модуль Sub :: Override CPAN.За исключением этого, это будет делать:

package Local::Patched::Foo;

use Exporter;

sub baz { print "GOOD baz!\n" };

sub import() {
    *Foo::baz = \&Local::Patched::Foo::baz;
}

1;
0 голосов
/ 12 августа 2010

Я бы посоветовал вам заменить поврежденный файл.

mkdir Something
cp Something.pm Something/Legacy.pm # ( or /Old.pm or /Bad.pm )

А затем перейти к этому файлу и отредактировать строку пакета:

package Something::Legacy;

Тогда у вас есть место, чтобы встать перед устаревшим кодом.Создайте новый Something.pm и получите все его экспорт:

use Something::Legacy qw<:all>;
our @EXPORT      = @Something::Legacy::EXPORT;
our @EXPORT_OK   = @Something::Legacy::EXPORT_OK;
our %EXPORT_TAGS = %Something::Legacy::EXPORT_TAGS;

После того, как все это будет в вашем текущем пакете, просто повторно внедрите подпрограмму.

sub bad_thing { ... }

Все, что у вас осталоськод, который вызывает Something::do_something, будет вызывать старый код через новый модуль.Любой устаревший код, вызывающий Something::bad_thing, будет вызывать новый код.

Также вы можете манипулировать *Something::Legacy другими способами.Если в вашем коде не используется локальный вызов, вам придется ввести &Something::Legacy::bad_thing.

my $old_bad_thing = \&Something::Legacy::bad_thing;
*Something::Legacy::bad_thing = \&bad_thing;

Таким образом, bad_thing по-прежнему может использовать это поведение, если это необходимо:

sub bad_thing { 
    ...
    eval { 
        $old_bad_thing->( @_ );
    };
    unless ( $EVAL_ERROR =~ /$hinky_message/ ) { 
        ...
    }
    ...
}
0 голосов
/ 12 августа 2010
package Local::Patched::Foo;
use Foo qw/:all_without_qux/; #see Exporter docs for tags or just list all functions
use Exporter 'import'; #modern way
our @EXPORT = qw( baz [... 100 more functions here ...] qux);
sub qux { print 2; }
1;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...