【问题标题】:Clarify definitions of "private" and "protected" in Ruby?澄清 Ruby 中“私有”和“受保护”的定义?
【发布时间】:2016-10-23 11:28:22
【问题描述】:

如果一个方法是受保护的,它可以被定义的any实例调用 类或其子类。如果一个方法是私有的,它只能在内部调用 调用对象的上下文——永远不可能访问另一个对象 直接使用对象的私有方法,即使对象属于同一类 作为调用者。

Ruby 编程,“类、对象和变量:Access Control

这个定义是我在搜索 ruby​​ 中私有方法和受保护方法的区别时从网上得到的。

我对此有两个疑问

class Abc
  def abc
    xyz
  end
 protected 
  def xyz
    p "hai"
  end
end

a=Abc.new
a.abc

在此我在对象a上隐式调用xyz,这就是我将如何调用xyz,即使它是私有的。所以“它可以被定义类的任何实例调用”这是什么意思??????

其次,

 class Abc
   def abc(obj)
     obj.xyz1
     obj.xyz2
     obj.xyz3
   end
 end

 class Xyz
   def xyz1
     p "called"
   end

  private 
   def xyz2
     p "called"
   end

  protected
   def xyz3
     p "called"
   end

 end

 a=Abc.new
 b=Xyz.new
 a.abc(b)

在此我可以在对象 a 上调​​用 obj b 的 xyz1 方法。但我不能在对象 a 上调​​用 obj b 的受保护 xyz3 和私有方法 xyz2 方法。所以“永远不可能直接访问另一个对象的私有方法,即使对象与调用者属于同一类。”这到底是什么意思???

谁能用更好的例子帮助我理解这一点????

【问题讨论】:

    标签: ruby-on-rails ruby access-specifier


    【解决方案1】:

    考虑受保护方法和私有方法的最佳方式是它们如何影响您是否可以在方法调用之前显式指定接收者,即将其视为技术规则,而不是一些元思想。

    1. 私有方法:不能在私有方法调用前显式指定接收者。 曾经(见评论)。因为 ruby​​ 总是调用带有某个接收者的方法,所以 ruby​​ 使用当前分配给 self 变量的任何对象作为接收者。

    2. 受保护方法:在某些情况下,您可以显式指定受保护方法调用的接收者。

    那么这只是探索 ruby​​ 何时允许您在受保护的方法调用之前显式指定接收器的问题:

    class Dog
      def execute_pro(dog)
        dog.pro_meth
      end
    
      protected
      def pro_meth
        puts 'protected'
      end
    end
    
    d1 = Dog.new
    p d1.protected_methods
    d1.pro_meth
    
    --output:--
    [:pro_meth]
    
    1.rb:15:in `<main>': protected method `pro_meth' called for #<Dog:0x007f8ef90e0eb8> (NoMethodError)
    

    上面的例子证明了语句:

    如果一个方法是受保护的,它可以被任何实例调用 定义类或其子类。

    太笼统了。但是:

    class Dog
      def execute_pro(dog)
        dog.pro_meth
      end
    
      protected
      def pro_meth
        puts 'protected'
      end
    end
    
    d1 = Dog.new
    d2 = Dog.new
    p d1.protected_methods
    
    d1.execute_pro d2
    
    --output:--
    [:pro_meth]
    protected
    

    永远不可能直接访问另一个对象的私有方法,即使该对象与调用者属于同一类。

    在上面的例子中,调用者是d1,在d1调用的方法中你可以直接访问d2的受保护方法,即你可以显式指定一个受保护方法调用前面的接收者,因为 d2 与 d1 属于同一类。您将无法直接访问 d2 的私有方法,因为您 永远不会 无法在私有方法调用之前显式指定接收者。这可能会让你相信……“好吧,私有和受保护是一回事:如果方法是私有的,我只需要删除显式接收器:

    class Dog
      def execute_pro(dog)
        pro_meth #<*****CHANGE HERE
      end
    
      private   #<****CHANGE HERE
      def pro_meth
        puts 'protected'
      end
    end
    
    d1 = Dog.new
    d2 = Dog.new
    
    d1.execute_pro d2
    
    --output:--
    protected
    

    输出是一样的……但结论是,“私有和受保护是一回事:如果方法是私有的,我只需要删除显式接收器。”是错的。这个例子太简单了,它掩盖了私有和受保护之间的细微差别。这是一个更好的例子:

    class Dog1
      def initialize(num)
        @num = num
      end
    
      def execute_pro(dog)
        dog.pro_meth 
      end
    
      protected  
      def pro_meth
        puts "protected -> #{@num}"
      end
    end
    
    d1 = Dog1.new 1
    d2 = Dog1.new 2
    
    d1.execute_pro d2
    
    class Dog2
      def initialize(num)
        @num = num
      end
    
      def execute_pro(dog)
        pro_meth  #<****CHANGE HERE
      end
    
      private  #<******CHANGE HERE
      def pro_meth
        puts "protected -> #{@num}"
      end
    end
    
    d1 = Dog2.new 1
    d2 = Dog2.new 2
    
    d1.execute_pro d2
    
    --output:--
    protected -> 2
    protected -> 1
    

    输出表明,您不能仅仅将受保护的方法转换为私有方法,然后从方法调用中移除显式接收器并始终获得相同的结果。在这种情况下,ruby 允许您在受保护的方法调用之前显式指定接收器,以便让您将方法调用定向到正确的对象。但是受保护的方法与公共方法不同:

    class Dog
      def execute_pro(dog)
        dog.pro_meth
      end
    
      protected
      def pro_meth
        puts 'protected'
      end
    end
    
    class Poodle < Dog
      def dostuff(dog)
        dog.pro_meth
      end
    end
    
    class Cat
      def dostuff(dog)
        dog.pro_meth
      end
    end
    
    d1 = Dog.new
    p1 = Poodle.new
    c1 = Cat.new
    
    p1.dostuff d1
    c1.dostuff d1
    
    --output:--
    protected
    
    1.rb:21:in `dostuff': protected method `pro_meth' called for #<Dog:0x007fe9040d28f8> (NoMethodError)
        from 1.rb:30:in `<main>'
    

    c1.dostuff d1这一行中,调用者是c1,在c1调用的方法内部,不能调用Dog的protected方法——因为Cat类不是Dog类的同一个类或子类。另请注意,如果您尝试删除显式接收器:

    class Cat
      def dostuff(dog)
        pro_meth  #<****CHANGE HERE
      end
    end 
    

    也不会调用 Dog 的 protected 方法,因为在 dostuff() 内部,ruby 将调用者分配给 self 变量,调用者是 c1,所以你得到:

    class Cat
      def dostuff(dog)
        #self = c1
        pro_meth
      end
    end 
    
    ...
    c1.dostuff d1
    

    并且ruby将方法调用pro_meth转换为self.pro_meth,相当于c1.pro_meth,Cat类没有定义名为pro_meth的方法。

    【讨论】:

    【解决方案2】:

    私有方法

    要定义一个私有方法,我们使用private关键字,它实际上是在一个名为Module的类中实现的内置方法。私有方法只能被定义它的类(或其子类之一)中的另一个方法调用。

    class Koan
      def call_say_koan
        say_koan
      end
    
      private
        def say_koan
          puts "What is the sound of one hand clapping?"
        end
    end
    
    k = Koan.new
    k.say_koan    # Output: NoMethodError: private method `say_koan' called for #<Koan:0x000000021e7380>
    k.call_say_koan        # Output: What is the sound of one hand clapping?
    

    在上面的例子中,我们不能直接(从类外部)调用 say_koan 私有方法,但我们可以调用 call_say_koan 公有方法,而后者又称为 say_koan。

    同样在上面的例子中,内置的私有方法没有使用参数。因此,它下面定义的所有方法都是私有的。

    私有方法也可以与之前定义的方法名称(作为符号传递)一起用作参数。

    class Foo
      def some_method
      end
    
      private :some_method
    end
    

    为了使类方法私有,请使用 private_class_method 关键字/方法而不是私有。

    不能用接收器调用私有方法,例如 self.尝试在 call_say_koan 中以 self 作为接收者 (self.say_koan) 调用 say_koan 方法会导致以下异常:

    NoMethodError: private method `say_koan' called for #<Koan:0x000000021eb548>
    

    从 Ruby 2.0 开始,respond_to?当给定私有方法作为参数时,方法将返回 false。

    k.respond_to? :say_koan  # Output: => false
    

    要列出类中的所有私有实例方法,请使用 private_instance_methods 内置方法。对于私有类方法,请使用 private_methods。

    Koan.private_instance_methods(false)  # Output => [:say_koan]
    

    受保护的方法

    要定义受保护的方法,我们使用受保护的关键字(实际上是一个方法)。与私有方法一样,受保护方法也可以由定义它的类(或其子类之一)中的其他方法调用。不同的是,受保护的方法也可以从同一类的其他实例中调用。

    没有受保护的类方法,Ruby 只支持受保护的实例方法。

    假设我们需要选择一些冥想者来参与一项研究。要找到最有经验的冥想者,我们需要比较他们的总冥想时间。但是,我们不希望显示小时数。

    class Meditator
      def initialize(hours)
        @hours = hours
      end
    
      def more_experienced?(other_person)
        hours > other_person.hours
      end
    
      protected
        attr_reader :hours  # We have made the accessor protected
    end
    
    m1 = Meditator.new 3000
    m2 = Meditator.new 5000
    
    m2.more_experienced? m1  # Output: => true
    m1.more_experienced? m2  # Output: => false
    

    类似的代码可用于保护任何类型的敏感数据免受外部访问(在类及其实例之外),尽管在 Ruby 中不常用受保护的方法。

    当不带参数调用时(如上例所示),受保护的方法会将其下定义的所有方法转换为受保护的方法。它还可以用于保护之前定义的方法,如下例所示。

    class Foo
      def some_method
      end
    
      protected :some_method
    end
    

    要列出类中所有受保护的实例方法,请使用 protected_instance_methods 内置方法。对于受保护的类方法,请使用 protected_methods。

    Meditator.protected_instance_methods(false)  # Output: => [:hours]
    

    【讨论】:

      【解决方案3】:

      您在某处选择的示例确实令人困惑(尤其是名称为 abc 或 xyz,几乎没有上下文)。请允许我使用一个不同的例子。

      理论是这样的:

      – Private 和 Protected 都可以通过公共方法从类外部访问。

      Protected 和 Private 的区别是:

      – 不能用接收者调用私有方法(甚至不能用#self)。除非……调用 Private setter 方法。如果您尝试删除接收器,Ruby 将创建一个局部变量。在这种情况下,自我是必须的。

      – 受保护的可能会或可能不会使用 #self 等接收器。

      --protected 可以访问来自同一个类的另一个对象的 protected 方法,Private 不能。 (参见方法 #eat_more_than(other) 的代码)

      现在说到继承……

      ——私有方法只能在子类上隐式调用(只是方法的名称),但不能显式调用(使用#self)。

      – 可以通过两种方式调用protected(有或没有#self || 隐式或显式)。

      让我们看看下面的例子:

       class Dog
        attr_accessor :name, :age
      
        def initialize(n, a)
          self.name = n
          self.age = a
        end
      
        def accessing_private
          "#{self.name} in human years is #{human_years}. This is secret!"
        end
      
        def accessing_protected
          "Will this work? " + a_protected_method
        end
      
        def eat_more_than(other) 
        # accessing other instance's protected method from the same class
          daily_diet < other.daily_diet 
          "#{name} eats more than #{other.name}"
        end
      
        def boy 
          gender_method("boy") # accessing private setter method
        end
      
        protected
      
        def daily_diet 
          age * 2 # the younger, the more they have to eat 
        end
      
        def a_protected_method
          "Yes, I'm protected!"
        end
      
        private
      
        attr_writer :gender
      
        def gender_method(gender)
          self.gender = gender # private setter method requires self
          "#{name} is a #{gender}"
        end
      
        def human_years
          age * 8
        end
      end
      
      # Create the first object of Dog
      blake = Dog.new("Blake", 5)
      
      p blake.accessing_private # "Blake in human years is 16. This is secret!"
      
      p blake.accessing_protected # "Will this work? Yes, I'm protected!"
      
      # Create the second object of Dog
      jackson = Dog.new("Jackson", 1)
      
      # Below, protected methods from different objects of the same type/class 
      # are proven to share access
      p jackson.eat_more_than(blake) # true -> "Jackson eats more than Blake"
      
      # Below, accessing private setter method through a public method.
      p blake.boy # Blake is a boy 
      

      现在在子类中,您不能使用接收器调用继承的私有方法,但无论哪种方式受保护都可以正常工作(有或没有接收器):

      class Puppy < Dog 
      
        def accessing_protected_explicitly
          "Explicitly calls '#{self.a_protected_method}'"
        end
      
        def accessing_protected_implicitly
          "Implicitly calls '#{a_protected_method}'"
        end 
      
        def accessing_private_implicitly 
          "#{self.name} is #{human_years} years old in human years. This is a secret!" # implicit
        end 
      
        def accessing_private_explicitly
          "#{self.name} is #{self.human_years} years old in human years" # explicit -> error
        end 
      end 
      
      # Below, testing them on a subclass 
      booboo = Puppy.new("Booboo", 1 )
      p booboo.accessing_protected_explicitly # works 
      p booboo.accessing_protected_implicitly # works
      p booboo.accessing_private_implicitly # works 
      p booboo.accessing_private_explicitly # error, called on a receiver
      

      希望对您有所帮助!

      【讨论】:

        猜你喜欢
        • 2010-11-02
        • 2012-01-09
        • 2020-11-07
        • 2011-09-15
        • 2011-07-09
        • 2015-08-11
        • 2014-04-26
        • 1970-01-01
        • 2011-08-18
        相关资源
        最近更新 更多