2017-12-18 20 views
6

Moo::Role을 사용하면 순환 가져 오기가 자동으로 내 메서드의 before 수정자가 실행되는 것을 방지하고 있음을 발견했습니다. 처리 방법 : 순환 수입 때문에 Moo :: Role의 'before'수정자가 자동으로 건너 뛰었습니까?

내가 가지고 Moo::Role MyRole.pm에서 :

package MyRole; 
use Moo::Role; 
use MyB; 
requires 'the_method'; 
before the_method => sub { die 'This has been correctly executed'; }; 
1; 

... MyA.pm에서 소비자 : MyB.pm에서

package MyA; 
use Moo; 
with ('MyRole'); 
sub the_method { die; } 
1; 

.. 그리고 또 다른 :

package MyB; 
use Moo; 
with ('MyRole'); 
sub the_method { die 'The code should have died before this point'; } 
1; 

내가 실행 이 script.pl :

#!/usr/bin/env perl 
package main; 
use MyA; 
use MyB; 
MyB->new()->the_method(); 

... 나는 The code should have died before this point at MyB.pm line 4.을 얻지 만 This has been correctly executed at MyRole.pm line 5을 볼 것으로 예상됩니다.

이 문제는 순환 가져 오기 때문에 발생한다고 생각합니다. script.pl에있는 use 문의 순서를 변경하거나 MyRole.pm에있는 use MyB;require 내에서 the_method 사이로 변경하면 문제가 해결됩니다.

이 동작이 필요합니까? 그렇다면 순환 수입을 피할 수없는 곳에서 처리하는 가장 좋은 방법은 무엇입니까?

문제를 해결할 수 있지만 실수로 쉽게 트리거 할 수 있습니다 (특히 검사 코드가 포함 된 before 기능이 자동으로 건너 뜁니다).

(나는 무 버전 2.003004를 사용하고 있습니다. 분명히 MyRole.pmuse MyB;는 불필요한 여기하지만이 생식 예를 들어, 코드를 단순화 한 후에 만입니다.)

답변

3

원형 수입은 오히려 힘들 수 있지만, 지속적으로 행동 할 수 . 결정적인 포인트는

  1. use Some::Module
  2. 모듈이로드 BEGIN { require Some::Module; Some::Module->import }처럼 동작

    , 그것은 컴파일 및 실행됩니다. BEGIN 블록이 주변 코드의 구문 분석 중에 실행됩니다.
  3. 각 모듈은 require 번만입니다. 다시 필요하면 require입니다.이 무시됩니다.

네 개의 파일을 BEGIN 블록에 require 개의 파일이 포함 된 단일 파일로 결합 할 수 있습니다.

의 당신의 기본 파일 시작하자 :

use MyA; 
use MyB; 
MyB->new()->the_method(); 

우리는 useBEGIN { require ... }에 변환하고 MyA 내용을 포함 할 수 있습니다. 명확하게하기 위해이 경우에는 관련이 없으므로 MyAMyB에있는 모든 ->import 통화는 무시합니다.

BEGIN { # use MyA; 
    package MyA; 
    use Moo; 
    with ('MyRole'); 
    sub the_method { die; } 
} 
BEGIN { # use MyB; 
    require MyB; 
} 
MyB->new()->the_method(); 

with('MyRole')는 또한 우리가 명시 적으로 만들 수있는 require 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(); 

우리는 그 다음도 MYB의 with('MyRole')을 확대 use MyB을 확장 할 수 있습니다 :

... 
    require MyRole; 
    with('MyRole '); 

은 그래서 그 확장하자를 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에 영향을주지 않습니다.


어떻게 피할 수 있습니까? 이 경우에는 use을 피하는 것이 도움이됩니다. 왜냐하면 현재 모듈이 초기화되기 전에 구문 분석을 인터럽트하기 때문입니다. 이것은 직관적이지 못한 행동으로 이어집니다.

use 모듈이 클래스 일 뿐이고 소스 코드가 구문 분석되는 방식 (예 : 서브 루틴 가져 오기)에 영향을주지 않으면 실행 시간을 지연시킬 수 있습니다. 최상위 코드가 실행되는 모듈의 런타임뿐만 아니라 주 응용 프로그램의 런타임까지. 즉, require을 가져온 클래스를 사용해야하는 서브 루틴에 고정해야합니다. require에는 필요한 모듈이 이미 가져 왔을 때도 여전히 약간의 오버 헤드가 있기 때문에 state $require_once = require Some::Module과 같은 요구 사항을 보호 할 수 있습니다. 그런 식으로, 요구에는 런타임 오버 헤드가 없습니다.

일반적으로 모듈의 최상위 코드에서 가능한 한 작은 초기화를 수행하면 많은 문제를 피할 수 있습니다. 게으른 것을 선호하고 그 초기화를 연기하십시오. 한편,이 게으름은 시스템을보다 역동적이고 예측하기 어렵게 만들 수 있습니다. 이미 초기화가 어떤 일이 발생했는지 알기는 어렵습니다.

더 일반적으로 디자인에 대해 열심히 생각하십시오. 이 순환 종속성이 필요한 이유는 무엇입니까? 상위 수준 코드가 하위 수준 코드에 종속 된 계층화 된 아키텍처를 고수하거나 낮은 수준 코드가 상위 수준 인터페이스에 종속되는 종속성 역변환을 사용하도록 결정해야합니다. 두 가지를 섞으면 끔찍한 혼란이 생길 ​​것입니다 (전시 A :이 질문).

일부 데이터 모델에는 반드시 동시 재귀 클래스가 있습니다. 이 경우 상호 의존 클래스를 단일 파일에 배치하여 수동으로 순서를 정렬하는 것이 가장 명확 할 수 있습니다.