2009-10-14 8 views
25

Tôi mất một lúc để hiểu cách thức phương thức riêng tư hoạt động trong Ruby, và nó thực sự khiến tôi rất khó xử. Có ai biết nếu có lý do chính đáng để các phương pháp riêng tư được xử lý theo cách của họ? Đó có phải là lý do lịch sử không? Hoặc lý do thực hiện? Hoặc là có lý do hợp lý vững chắc tốt (nghĩa là ngữ nghĩa)?Có lý do chính đáng để 'tư nhân' hoạt động theo cách của Ruby?

Ví dụ: Hành vi

class Person 
    private 
    attr_reader :weight 
end 

class Spy < Person 
private 
    attr_accessor :code 
public 
    def test 
    code   #(1) OK: you can call a private method in self 
    Spy.new.code #(2) ERROR: cannot call a private method on any other object 
    self.code  #(3) ERROR!!! cannot call a private method explicitly on 'self' 
    code="xyz" #(4) Ok, it runs, but it actually creates a local variable!!! 
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok 
    weight  #(6) OK! You can call a private method defined in a base class 
    end 
end 
  • Ruby trên đường (1), (2) và (5) có vẻ hợp lý.
  • Thực tế là (6) là ok là một chút lạ, đặc biệt là đến từ Java và C++. Có lý do chính đáng nào cho việc này không?
  • Tôi thực sự không hiểu tại sao (3) không thành công! Một lời giải thích, bất cứ ai?
  • Sự cố trên dòng (4) trông giống như sự mơ hồ trong ngữ pháp, không liên quan gì đến 'riêng tư'.

Bất kỳ ý tưởng nào?

+1

Tôi thực sự tò mò muốn biết lý do đằng sau các quyết định thiết kế là tốt. Câu trả lời của tôi chỉ là một lời giải thích làm sáng tỏ quan niệm sai lầm của bạn. Nó giải thích cung cấp một chút về cách thức, nhưng không phải là lý do tại sao. – EmFi

Trả lời

30

Bạn có thể tìm thấy nó hữu ích để đọc định nghĩa của public, private and protected. ruby ​​của (Skip to Access Control)

tin của Ruby là tương tự như bảo vệ của Java. Không có Ruby tương đương với Java riêng tư. EDIT: Giải pháp này hiện cung cấp một phương thức giả mạo nó là lý tưởng riêng tư của Java về các đối tượng Ruby.

Riêng tư được định nghĩa là phương thức/biến có thể chỉ được gọi là được gọi ngầm. Đây là lý do tại sao các câu 2 và 3 thất bại. Nói cách khác, các phương thức/biến giới hạn riêng cho ngữ cảnh của một lớp hoặc lớp con mà chúng được định nghĩa. Thừa kế truyền các phương thức riêng cho các lớp con và do đó có thể được truy cập với một bản thân ngầm. (Giải thích lý do tại sao câu lệnh 6 hoạt động.)

Tôi nghĩ bạn đang tìm kiếm thứ gì đó gần hơn để được bảo vệ. Hoạt động tương tự như các trình truy cập Java không được hiển thị (ví dụ: công khai, riêng tư, được bảo vệ) Bằng cách thay đổi riêng tư trong Spy để bảo vệ tất cả 6 báo cáo của bạn hoạt động. Các phương thức được bảo vệ có thể được gọi bởi bất kỳ cá thể nào của lớp xác định hoặc các lớp con của chúng. Hoặc được gọi một cách rõ ràng hoặc ngầm được gọi là các câu lệnh hợp lệ cho các phương thức được bảo vệ miễn là người gọi là lớp của đối tượng phản hồi cuộc gọi hoặc thừa kế từ nó.

class Person 
    private 
    attr_reader :weight 
end 

class Spy < Person 
protected 
    attr_accessor :code 
public 
    def test 
    code   #(1) OK: you can call a private method in self 
    Spy.new.code #(2) OK: Calling protected method on another instance from same class family or a descendant. 
    self.code  #(3) OK: Calling protected method on with explicit self is allowed with protected 
    code="xyz" #(4) Ok, it runs, but it actually creates a local variable!!! 
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok 
    weight  #(6) OK! You can call a private method defined in a base class 
    end 
end 

s = Spy.new 
s.test # succeeds 
s.code #(7) Error: Calling protected method outside of the class or its descendants. 

Theo tuyên bố 4. Bạn đúng khi giả định điều này là để tránh sự mơ hồ. Nó là một biện pháp bảo vệ nguy cơ tiềm năng của bản chất năng động của Ruby. Nó đảm bảo rằng bạn không thể ghi đè người truy cập bằng cách mở lại lớp sau. Một tình huống có thể phát sinh, ví dụ bằng cách đánh giá mã độc.

Tôi chỉ có thể suy đoán về các quyết định thiết kế đã dẫn đến những hành vi này. Đối với hầu hết nó tôi cảm thấy nó đi xuống đến bản chất năng động của ngôn ngữ.

P.S. Nếu bạn thực sự muốn cung cấp cho mọi thứ định nghĩa java riêng tư. Chỉ có sẵn cho lớp mà nó được xác định, thậm chí không phải lớp con. Bạn có thể thêm một phương thức self.inherited vào các lớp của bạn để loại bỏ các tham chiếu tới các phương thức mà bạn muốn giới hạn truy cập.

Làm trọng lượng thuộc tính không thể tiếp cận từ lớp con:

class Person 
    private 
    attr_reader :weight 

    def initialize 
    @weight = 5 
    end 

    def self.inherited(subclass) 
    subclass.send :undef_method, :weight 
    end 
end 

class Spy < Person 
private 
    attr_accessor :code 
public 
    def test 
    weight  
    end 
end 

Person.new.send(:weight) # => 5 
Spy.new.send(:weight) #=> Unhelpful undefined method error 

Nó có thể làm cho ý nghĩa hơn để thay thế các cuộc gọi undef_method một cái gì đó như thế này:

def self.inherited(subclass) 
    subclass.class_eval %{ 
     def weight 
     raise "Private method called from subclass. Access Denied" 
     end 
    } 
    end 

nào cung cấp một lỗi nhiều hữu ích hơn và cùng chức năng.

Gửi là cần thiết để có được xung quanh gọi phương thức riêng tư cho các lớp khác. Chỉ được sử dụng để chứng minh rằng mọi thứ đang thực sự hoạt động.

Điều quan trọng là làm cho riêng tư và được bảo vệ vô dụng. Nếu bạn thực sự nghiêm túc về việc bảo vệ các phương pháp của bạn, bạn sẽ phải ghi đè lên gửi để chặn chúng. Các mã sau nào đó dựa trên private_methods của đối tượng:

def send_that_blocks_private_methods(method, *args) 
    if private_methods.include?(method.to_s) 
    raise "Private method #{method} cannot called be called with send." 
    else 
    send_that_allows_private_methods(method, *args) 
    end 
end 

alias_method :send_that_allows_private_methods, :send 
alias_method :send, :send_that_blocks_private_methods 
private :send_that_allows_private_methods 

Bạn có thể chỉ định một class_variable của private_methods bạn muốn chặn truy cập vào thay vì từ chối quyền truy cập vào tất cả các phương pháp tư nhân. Bạn cũng có thể gửi riêng tư, nhưng có cách sử dụng hợp pháp gọi điện gửi từ bên ngoài một đối tượng.

+0

+1 Cảm ơn câu trả lời của bạn: nó rất rõ ràng. – MiniQuark

+0

"Mà trong hindsight, làm cho tư nhân và bảo vệ vô dụng." Không hẳn. Họ có giá trị giao tiếp. Nếu tôi làm cho 'Foo # things' công khai, tôi đang nói" sử dụng cái này "; nếu riêng tư, tôi nói "không phụ thuộc vào điều này; đó là chi tiết triển khai có thể thay đổi". Nếu Ruby sẽ cho phép bạn mở lại 'Foo' và ** xác định lại **' # things', nó cũng có thể cho phép bạn ** truy cập ** phương thức. Nhưng nó làm cho bạn sử dụng 'gửi' để bạn biết bạn đang làm điều gì đó bất ngờ. Tôi nghĩ đây chỉ là triết lý của Ruby. –

+0

Có phải 'send_that_blocks_private_methods' tương tự như [' Object # public_send'] (https://ruby-doc.org/core-2.4.1/Object.html#method-i-public_send) không? –