2012-01-10 45 views
5

này được giải thích tốt nhất với một ví dụ:Phương thức nhập của Ruby có luôn riêng tư không?

file1.rb:

def foo 
    puts 123 
end 

file2.rb:

class A 
    require 'file1' 
end 
A.new.foo 

sẽ đưa ra một thông báo lỗi "': phương pháp tư nhân 'foo' gọi là" .

Tôi có thể giải quyết vấn đề này bằng cách thực hiện A.new.send("foo") nhưng có cách nào để làm cho phương thức nhập được công khai không?

Chỉnh sửa: Để làm rõ, tôi không nhầm lẫn bao gồm và yêu cầu. Ngoài ra, lý do tại sao tôi không thể sử dụng bao gồm bình thường (như nhiều người đã chỉ ra đúng) là đây là một phần của một thiết lập siêu lập trình. Tôi cần cho phép người dùng thêm chức năng vào thời gian chạy; ví dụ: anh ta có thể nói "run-this-app --include file1.rb" và ứng dụng sẽ hoạt động khác nhau dựa trên mã mà anh đã viết trong tệp1.rb. Xin lỗi đã giải thích rõ ràng hơn.

Chỉnh sửa: Sau khi đọc câu trả lời của Jorg, tôi nhận ra mã của tôi không hoạt động chính xác như dự định, và anh ấy trả lời hoàn toàn câu hỏi của tôi (sai lầm). Tôi đang cố gắng làm điều gì đó giống như str=(entire file1.rb as string); A.class_exec(str).

+0

Bạn đã thử 'A.new.instance_eval {foo}' chưa? Nó không làm việc cho tôi (ruby 1.9.2). – knut

+0

Bạn có nhầm lẫn với 'require with' include' không? –

+0

@knut tôi đã làm. Nó hoạt động. – alexloh

Trả lời

7

thủ tục toàn cầu trong Ruby không thực sự thủ tục toàn cầu. Chúng là những phương pháp, giống như mọi thứ khác. Cụ thể, khi bạn xác định những gì trông giống như thủ tục toàn cầu, bạn là thực sự là xác định phương thức cá nhân riêng lẻ là Object. Vì mọi đoạn mã trong Ruby được đánh giá trong ngữ cảnh của một đối tượng, điều này cho phép bạn sử dụng các phương thức đó như là các thủ tục chung, vì self là bộ nhận mặc định và self là một đối tượng có lớp kế thừa từ Object.

Vì vậy, đây:

# file1.rb 

def foo 
    puts 123 
end 

thực sự là tương đương với

# file1.rb 

class Object 
    private 

    def foo 
    puts 123 
    end 
end 

Bây giờ bạn có một "quy trình toàn cầu" được gọi foo, mà bạn có thể gọi như thế này:

foo 

Lý do lý do bạn có thể gọi nó như thế này, là cuộc gọi này thực sự là tương đương với

self.foo 

self là một đối tượng bao gồm Object trong chuỗi gốc của nó, do đó nó được thừa hưởng các foo phương pháp riêng.

[Lưu ý: chính xác, không thể gọi phương thức riêng tư với người nhận rõ ràng, ngay cả khi người nhận rõ ràng là self. Vì vậy, để được thực sự gàn dở, nó là thực tương đương với self.send(:foo) và không self.foo]

Các A.new.foo trong bạn file2.rb là một cá trích đỏ:. Bạn có thể chỉ cần cũng cố gắng Object.new.foo hoặc [].foo hay 42.foo và nhận được kết quả tương tự .

Bằng cách này: putsrequire là mình ví dụ như vậy "thủ tục toàn cầu", mà thực sự là phương pháp tin trên Object (hay chính xác hơn, họ có phương pháp riêng trên Kernel được trộn vào Object).

Trên một sidenote: nó là thực sự phong cách xấu để đặt cuộc gọi đến require bên trong một định nghĩa lớp, bởi vì nó làm cho nó trông giống như mã require d được bằng cách nào đó scoped hoặc namespaced bên trong lớp, đó là tất nhiên sai. require chỉ cần chạy mã trong tệp, không có gì khác.

Vì vậy, trong khi

# file2.rb 

class A 
    require 'file1.rb' 
end 

là mã hoàn toàn hợp lệ, nó cũng rất khó hiểu. Nó là tốt hơn để sử dụng sau, ngữ nghĩa tương đương, mã:

# file2.rb 

require 'file1.rb' 

class A 
end 

Bằng cách đó nó là hoàn toàn rõ ràng đối với người đọc của mã mà file1.rb là không có cách nào scoped hoặc namespaced bên A.

Ngoài ra, thường được ưu tiên rời khỏi đuôi tệp, tức là sử dụng require 'file1' thay vì require 'file1.rb'. Điều này cho phép bạn thay thế tệp Ruby bằng, ví dụ, mã gốc (cho MRI, YARV, Rubinius, MacRuby hoặc JRuby), mã byte JVM trong một tệp .jar hoặc .class (cho JRuby), mã byte CIL trong .dll tệp (cho IronRuby) và vân vân, mà không phải thay đổi bất kỳ cuộc gọi nào trong số các cuộc gọi require của bạn.

Một nhận xét cuối cùng: cách thành ngữ để tránh né bảo vệ truy cập là sử dụng send, không phải instance_eval, tức là sử dụng A.new.send(:foo) thay vì A.new.instance_eval {foo}.

+0

Cảm ơn bạn! Câu trả lời này thực sự hoàn chỉnh và xóa bỏ những hiểu lầm mà tôi có --- điều đó đòi hỏi giống như C bao gồm văn bản được sao chép. Có cách nào để thêm các nội dung của file1.rb như là phương pháp của lớp A động?Cả nội dung và vị trí của file1 chỉ được biết trong thời gian chạy và người dùng không biết về A. Hoặc tôi có thể tìm thấy danh sách các phương thức hoặc mô-đun được xác định trong một tệp không? – alexloh

10

Đây là cách không tốt để thực hiện việc này trong Ruby. Hãy thử sử dụng mixins qua module thay vì:

file1.rb:

module IncludesFoo 
    def foo 
    puts 123 
    end 
end 

file2.rb:

require 'file1.rb' 

class A 
    include IncludesFoo 
end 

A.new.foo 
# => 123 
+0

Cả hai 'file1.rb' và nội dung của nó đến từ đầu vào của người dùng và được tải động. Về cơ bản người dùng có thể nhập đường dẫn vào thời gian chạy và ứng dụng sẽ tải tệp ruby ​​thích hợp sẽ thay đổi chức năng hiện có, mà không cần khởi động lại, v.v. Rất tiếc, điều này đã rõ ràng hơn. – alexloh

0

Còn khoảng load("file1", A) thì sao? (RDoc link)

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