2008-10-15 75 views
17

Theo nàyTại sao tôi không nên sử dụng UNIVERSAL :: isa?

http://perldoc.perl.org/UNIVERSAL.html

tôi không nên sử dụng UNIVERSAL :: isa() và thay vào đó nên sử dụng $ obj-> isa() hoặc CLASS-> isa().

này có nghĩa là để tìm hiểu xem cái gì đó là một tài liệu tham khảo ở nơi đầu tiên và sau đó là tham chiếu đến lớp học này tôi phải làm

eval { $poss->isa("Class") } 

và kiểm tra $ @ và tất cả những gì gumph, nếu không

use Scalar::Util 'blessed'; 
blessed $ref && $ref->isa($class); 

Câu hỏi của tôi là lý do tại sao? Có gì sai với UNIVERSAL :: isa được gọi là như vậy? Nó sạch hơn nhiều cho những việc như:

my $self = shift if UNIVERSAL::isa($_[0], __PACKAGE__) 

Để xem chức năng này có được gọi trên vật thể hay không. Và có một thay thế sạch đẹp mà không nhận được cồng kềnh với ampersands và dòng dài tiềm năng?

Trả lời

33

Vấn đề chính là nếu bạn gọi trực tiếp số UNIVERSAL::isa, bạn sẽ bỏ qua bất kỳ lớp nào đã quá tải isa. Nếu những lớp đó dựa vào hành vi quá tải (mà chúng có thể làm hoặc nếu không chúng sẽ không ghi đè nó), thì đây là một vấn đề. Nếu bạn gọi isa trực tiếp trên đối tượng may mắn của bạn, thì phương thức isa chính xác sẽ được gọi trong cả hai trường hợp (quá tải nếu nó tồn tại, UNIVERSAL :: nếu không).

Vấn đề thứ hai là UNIVERSAL::isa sẽ chỉ thực hiện kiểm tra bạn muốn trên tham chiếu may mắn giống như mọi cách sử dụng khác của isa. Nó có hành vi khác nhau đối với các tham chiếu không may mắn và vô hướng đơn giản. Vì vậy, ví dụ của bạn mà không kiểm tra xem $ref là may mắn là không làm điều đúng, bạn đang bỏ qua một điều kiện lỗi và sử dụng hành vi thay thế của UNIVERSAL. Trong một số trường hợp, điều này có thể gây ra lỗi tinh tế (ví dụ, nếu biến của bạn có chứa tên của một lớp).

xem xét:

use CGI; 

my $a = CGI->new(); 

my $b = "CGI"; 

print UNIVERSAL::isa($a,"CGI"); # prints 1, $a is a CGI object. 
print UNIVERSAL::isa($b,"CGI"); # Also prints 1!! Uh-oh!! 

Vì vậy, trong Tóm lại, không sử dụng UNIVERSAL::isa ... Do việc kiểm tra lỗi thêm và gọi isa trên đối tượng của bạn trực tiếp.

+0

UNIVERSAL :: isa KHÔNG yêu cầu tham chiếu may mắn. Nó hoạt động tuyệt vời trên các tham chiếu không có giá trị (ví dụ: UNIVERSAL :: isa ([7], 'ARRAY') trả về 1). Như đã giải thích trong tài liệu, nếu bạn chuyển nó thành một chuỗi, nó coi nó như một tên lớp (như được minh họa bởi ví dụ của bạn). – cjm

+0

@cjm: Bạn nói đúng, tôi nên làm rõ điều đó. –

+0

Tất nhiên, có một thời gian khi [bạn thực sự muốn vấn đề thứ hai] (http://www.perlmonks.org/?node_id=576091) ... nhưng bạn phải cẩn thận với những hạn chế khác, nếu đó là những gì bạn đang cố gắng làm. – Joe

7

Để trả lời trực tiếp câu hỏi của bạn, câu trả lời nằm ở cuối trang bạn đã liên kết, cụ thể là nếu gói xác định phương thức isa thì gọi số UNIVERSAL::isa trực tiếp sẽ không gọi phương thức gói isa. Đây là hành vi rất không trực quan từ quan điểm hướng đối tượng.

Phần còn lại của bài đăng này chỉ là câu hỏi thêm về lý do tại sao bạn làm điều này ngay từ đầu.

Trong mã như trên, trong trường hợp nào thử nghiệm isa cụ thể không thành công? tức là, nếu đó là một phương pháp, trong trường hợp nào thì đối số đầu tiên không phải là lớp gói hoặc một thể hiện của nó?

Tôi hỏi điều này vì tôi tự hỏi nếu có lý do chính đáng tại sao bạn muốn kiểm tra xem đối số đầu tiên có phải là một đối tượng ngay từ đầu không. tức là, bạn chỉ đang cố bắt những người nói rằng FooBar::method thay vì FooBar->method hoặc $foobar->method? Tôi đoán Perl không được thiết kế cho loại coddling đó, và nếu mọi người nhầm lẫn sử dụng FooBar::method họ sẽ sớm tìm ra được.

Số dặm của bạn có thể thay đổi.

9

Xem tài liệu cho UNIVERSAL::isaUNIVERSAL::can vì lý do bạn không nên làm điều đó.

Tóm lại, có các mô-đun quan trọng với nhu cầu chính hãng để ghi đè 'isa' (chẳng hạn như Test::MockObject) và nếu bạn gọi nó là một hàm, bạn sẽ phá vỡ điều này.

Tôi phải nói rằng, my $self = shift if UNIVERSAL::isa($_[0], __PACKAGE__) không trông cực kỳ sạch sẽ đối với tôi - những người ủng hộ Perl sẽ phàn nàn về nhiễu đường. :)

+0

Cảm ơn bạn đã giải thích "tại sao không" tốt hơn tôi!+1 –

2

Giả sử ví dụ về những gì bạn muốn có thể làm là trong một phương thức đối tượng, bạn đang bị hoang tưởng không cần thiết. Mục được truyền đầu tiên sẽ luôn là tham chiếu đến một đối tượng của lớp thích hợp (hoặc một lớp con) hoặc nó sẽ là tên của lớp (hoặc một lớp con). Nó sẽ không bao giờ là một tham chiếu của bất kỳ kiểu nào khác, trừ khi phương thức đã được cố ý gọi là một hàm. Do đó, bạn có thể sử dụng một cách an toàn ref để phân biệt giữa hai trường hợp.

if (ref $_[0]) { 
    my $self = shift; 
    # called on instance, so do instancey things 
} else { 
    my $class = shift; 
    # called as a class/static method, so do classy things 
} 
7

Mọi người khác đã nói với bạn tại sao bạn không muốn sử dụng UNIVERSAL::isa, bởi vì nó phá vỡ khi mọi việc quá tải isa. Nếu họ đã đi đến tất cả các thói quen quá tải mà phương pháp rất đặc biệt, bạn chắc chắn muốn tôn trọng nó. Chắc chắn, bạn có thể làm điều này bằng cách viết:

if (eval { $foo->isa("thing") }) { 
    # Do thingish things 
} 

eval đảm bảo để trả về false nếu nó ném một ngoại lệ, và giá trị cuối cùng ngược lại. Nhưng có vẻ như khủng khiếp và bạn không cần phải viết mã theo cách hài hước vì ngôn ngữ đó muốn bạn. Những gì chúng tôi thực sự muốn là viết chỉ:

if ($foo->isa("thing")) { 
    # Do thingish things 
} 

Để làm điều đó, chúng tôi phải đảm bảo rằng $foo luôn là một đối tượng. Nhưng $foo có thể là một chuỗi, một số, một tham chiếu, một giá trị không xác định, hoặc tất cả các loại công cụ kỳ lạ. Thật là một Perl xấu hổ không thể làm cho mọi đối tượng lớp học đầu tiên là mọi thứ.

Oh, chờ đợi, nó có thể ...

use autobox; # Everything is now a first class object. 
use CGI;  # Because I know you have it installed. 

my $x = 5; 
my $y = CGI->new; 

print "\$x is a CGI object\n" if $x->isa('CGI'); # This isn't printed. 
print "\$y is a CGI object\n" if $y->isa('CGI'); # This is! 

Bạn có thể lấy autobox từ CPAN. Bạn cũng có thể sử dụng nó với phạm vi từ vựng, vì vậy mọi thứ có thể là đối tượng lớp đầu tiên chỉ dành cho các tệp hoặc khối mà bạn muốn sử dụng ->isa() mà không cần phải chịu đựng thêm bất kỳ sự đau đầu nào. Nó cũng thực hiện một số nhiều hơn những gì tôi đã đề cập trong ví dụ đơn giản này.

+3

Cá nhân tôi thích sử dụng 'eval {$ foo-> isa (" điều ")}' thay vì mô đun 'autobox' không phổ biến, cũng ảnh hưởng đến hiệu suất. – codeholic

0

Phải. Nó làm một điều sai trái cho các lớp quá tải isa. Chỉ cần sử dụng thành ngữ sau:

if (eval { $obj->isa($class) }) { 

Nó dễ hiểu và thường được chấp nhận.

Các vấn đề liên quan