循环导入可能会变得相当棘手,但行为一致。关键点是:
use Some::Module
表现得像BEGIN { require Some::Module; Some::Module->import }
- 当一个模块被加载时,它被编译并执行。
BEGIN
块在解析周围代码期间执行。
- 每个模块只有
require
一次。当再次需要时,将require
被忽略。
知道了这一点,我们可以将您的四个文件组合成一个文件,其中包含require
BEGIN 块中的 d 文件。
让我们从您的主文件开始:
use MyA;
use MyB;
MyB->new()->the_method();
我们可以将其转换use
为BEGIN { require ... }
并包含MyA
内容。为清楚起见,我将忽略任何->import
调用MyA
,MyB
因为它们在这种情况下不相关。
BEGIN { # use MyA;
package MyA;
use Moo;
with ( 'MyRole' );
sub the_method { die; }
}
BEGIN { # use MyB;
require MyB;
}
MyB->new()->the_method();
with('MyRole')
也做了 a ,require MyRole
我们可以明确表示:
...
require MyRole;
with( 'MyRole ');
所以让我们扩展一下:
BEGIN { # use MyA;
package MyA;
use Moo;
{ # require MyRole;
package MyRole;
use Moo::Role;
use MyB;
requires 'the_method';
before the_method => sub { die 'This has been correctly executed'; };
}
with ( 'MyRole' );
sub the_method { die; }
}
BEGIN { # use MyB;
require MyB;
}
MyB->new()->the_method();
然后我们可以扩展use MyB
,也将 MyB 扩展with('MyRole')
为 a require
:
BEGIN { # use MyA;
package MyA;
use Moo;
{ # require MyRole;
package MyRole;
use Moo::Role;
BEGIN { # use MyB;
package MyB;
use Moo;
require MyRole;
with ( 'MyRole' );
sub the_method { die 'The code should have died before this point'; }
}
requires 'the_method';
before the_method => sub { die 'This has been correctly executed'; };
}
with ( 'MyRole' );
sub the_method { die; }
}
BEGIN { # use MyB;
require MyB;
}
MyB->new()->the_method();
在MyB
我们里面有一个require MyRole
,但是已经需要那个模块了。因此,这没有任何作用。在执行过程中,MyRole
仅包含以下内容:
package MyRole;
use Moo::Role;
所以这个角色是空的。requires 'the_method'; before the_method => sub { ... }
那时还没有编译。
结果MyB
构成了一个空角色,这不会影响the_method
.
如何避免这种情况?在这些情况下避免 a 通常很有帮助,use
因为这会在当前模块初始化之前中断解析。这会导致不直观的行为。
当你的模块use
只是类并且不影响你的源代码的解析方式(例如通过导入子例程)时,你通常可以推迟运行时间的要求。不仅是执行顶级代码的模块的运行时间,还包括主应用程序的运行时间。这意味着将您require
插入需要使用导入类的子例程中。由于require
即使已经导入了所需的模块, a 仍然有一些开销,因此您可以像state $require_once = require Some::Module
. 这样,require 就没有运行时开销。
一般来说:您可以通过在模块的顶级代码中进行尽可能少的初始化来避免许多问题。更喜欢懒惰并推迟初始化。另一方面,这种惰性也会使您的系统更加动态且难以预测:很难判断已经发生了什么初始化。
更一般地说,认真考虑您的设计。为什么需要这种循环依赖?您应该决定要么坚持高级代码依赖于低级代码的分层架构,要么使用低级代码依赖于高级接口的依赖倒置。将两者混合会导致可怕的混乱(图表A:这个问题)。
我确实了解某些数据模型必然具有共同递归类。在这种情况下,通过将相互依赖的类放在一个文件中来手动排序是最清楚的。