6

Foo::Base という名前の基本クラスがあり、「new」などのメソッドを継承し、スコープにいくつかのサブルーチン名をインポートする必要があります。

package Foo::Base;

sub new { ... }

sub import {
    no strict 'refs';

    my $caller = caller;

    *{"${caller}::my_sub"} = sub { 1 };
}

1;

したがって、この基本クラスを 2 番目のクラス Foo::Child で使用する必要があります。

use base 'Foo::Base';

...そして継承には機能しますが、スコープに「my_sub」をインポートしません。文字列を追加できます

use Foo::Base;

それは役に立ちますが、次のようなものは書きたくありません。

use base 'Foo::Base';
use Foo::Base;

これはちょっと奇妙に見えます...この問題に対する提案はありますか?

4

3 に答える 3

14

あなたがしていることをしたいと思う理由は 2 つありますが、どちらも悪いことです。

まず、親クラスからメソッドをインポートしようとしています...何らかの理由で。オブジェクト指向の仕組みを誤解しているかもしれません。その必要はありません。継承されたメソッドをメソッドとして呼び出すだけで、それらのメソッドが奇妙なことをしていない限り、問題なく動作します。

これは、一部がメソッドで一部がインポートされた関数である混合使用モジュールである可能性が高くなります。そしてそのためにあなたができること...

use base 'Foo::Base';
use Foo::Base;

そして、あなたはそれがちょっと奇妙に見えることを正しく観察しました... それはちょっと奇妙だからです. エクスポートも行うクラスにはイディオムが混在しているため、奇妙な使用パターンが発生します。

最善の方法は、関数をエクスポートする代わりに、クラスを再設計して、関数を独自のモジュールに分割するか、クラス メソッドにすることです。関数がクラスとあまり関係がない場合は、スピンオフするのが最善です。それらがクラスに関連している場合は、それらをクラス メソッドにします。

use base 'Foo::Base';

Foo::Base->some_function_that_used_to_be_exported;

これにより、インターフェイスの不一致が解消され、おまけとして、サブクラスは他のメソッドと同様にクラス メソッドの動作をオーバーライドできます。

package Bar;

use base 'Foo::Base';

# override
sub some_function_that_used_to_be_exported {
    my($class, @args) = @_;

    ...do something extra maybe...

    $class->SUPER::some_function_that_used_to_be_exported(@args);

    ...and maybe something else...
}

基本クラスを制御できない場合でも、エクスポートされた関数をメソッドに変換するサブクラスを作成することで、インターフェイスを正常にすることができます。

package SaneFoo;

use base 'Foo::Base';

# For each function exported by Foo::Base, create a wrapper class
# method which throws away the first argument (the class name) and
# calls the function.
for my $name (@Foo::Base::EXPORT, @Foo::Base::EXPORT_OK) {
    my $function = Foo::Base->can($name);
    *{$name} = sub {
        my $class = shift;
        return $function->(@_);
    };
}
于 2012-10-19T18:30:05.530 に答える
6

を書くときは、モジュールuse baseの機能を使用しています。baseそして、基本クラスにしたいモジュールのパラメーターを渡しています。

OO IS-A 関係では、インポートは必要ありません。OO-pattern: でメソッドを呼び出します$object_or_class->method_name( @args )。これは、次のように、呼び出し元が誰であるかを気にしないことを意味する場合があります。

sub inherited_util {
    my ( undef, @args ) = @_;
    ... 
}

また

sub inherited2 { 
    shift;
    ...
}

ただし、基本モジュールで定義されたユーティリティを使用し、そのモジュールで定義されたクラスの動作を継承する場合は、2 つの use ステートメントがまさにそれを示しています。

それでも、モジュールで使用したい 2 つの異なるタイプの動作がある場合は、ユーティリティ タイプのものを独自のモジュールに分割し、両方のモジュールから使用する方がよいでしょう。いずれにせよ、明示的な動作は暗黙の動作よりも優れていることがよくあります。

ただし、以前にこのパターンを使用したことがあります。

sub import { 
    shift;
    my ( $inherit_flag ) = @_;
    my $inherit 
        = lc( $inherit_flag ) ne 'inherit' ? 0
        : shift() && !!shift()             ? 1
        :                                    0
        ;
    if ( $inherit ) { 
        no strict 'refs';
        push @{caller().'::ISA'}, __PACKAGE__;
        ...
    }
    ...
}

そのようにして、使用法を明示的にまとめて 1 つの呼び出しを行います。

use UtilityParent inherit => 1, qw<normal args>;
于 2012-10-19T17:41:47.203 に答える
1

「しかし、なぜ Foo::Child のインポートが機能しないのですか?」という質問をもう少し明確にするために、実際に何が起こっているかを説明するプログラムを次に示します。

use Foo::Child;
print my_sub(); # Really? Yes, really!
print Foo::Child::my_sub()," done\n";

これを Foo::Base::import() ルーチンに追加しました:

print "Caller is $caller\n";

これを実行すると、次の出力が表示されます。

Caller is main
1
Undefined subroutine &Foo::Child::my_sub called at foo_user.pl line 4.

Foo::Base レポートが表示され、呼び出し元が誰であるかがわかります。これはメイン プログラムです。はい、main::my_sub が存在することを示す「1」が表示されます。その後、インポートが間違った名前空間に移動したために失敗します。

どうしてこれなの?インポート プロセスはすべてメイン プログラムによって処理されるためです。Foo::Base の import は、Foo::Child のどこからも呼び出されません。モジュールをロードする過程でメインプログラムによって呼び出されます。メソッドではなく、サブルーチンを強制的に Foo::Child にインポートさせたい場合は、Foo::Child 自体で明示的にインポートする必要があります。'use Foo::Base' はそれを行います。これにより、呼び出し元として Foo::Child でインポートが実行されます。インポートを主張するが、二重使用があまりにもあなたを怒らせる場合は、'base を使用' の直後に Foo::Base::import() を呼び出すことができます。とにかく、これはまさに二重の「使用」が行うことです。

とはいえ、私は Schwern のクラス メソッドの方が好きなので、その方法をお勧めします。

于 2012-10-19T21:29:41.927 に答える