是否可以在 Ruby 中比较私有属性?

发布于 2024-08-12 23:25:59 字数 289 浏览 6 评论 0原文

我心里想:

class X
    def new()
        @a = 1
    end
    def m( other ) 
         @a == other.@a
    end
end

x = X.new()
y = X.new()
x.m( y ) 

但这不起作用。

错误消息是:

syntax error, unexpected tIVAR

那么如何比较同一类的两个私有属性呢?

I'm thinking in:

class X
    def new()
        @a = 1
    end
    def m( other ) 
         @a == other.@a
    end
end

x = X.new()
y = X.new()
x.m( y ) 

But it doesn't works.

The error message is:

syntax error, unexpected tIVAR

How can I compare two private attributes from the same class then?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夏花。依旧 2024-08-19 23:25:59

对于您眼前的问题已经有几个很好的答案,但我注意到您的代码的其他一些部分值得评论。 (不过,大多数都是微不足道的。)

这里有四个微不足道的问题,它们都与编码风格相关:

  1. 缩进:您混合了 4 个空格用于缩进和 5 个空格。通常最好只坚持一种缩进风格,在 Ruby 中通常是 2 个空格。
  2. 如果方法不带任何参数,则通常在方法定义中省略括号。
  3. 同样,如果您发送不带参数的消息,则括号将被省略。
  4. 左括号之后和右括号之前没有空格,块中除外。

无论如何,这只是小事。最重要的是:

def new
  @a = 1
end

做你认为它做的事情!这定义了一个名为 X#new实例方法,而不是一个名为 X.new 的类方法!

您在这里调用的

x = X.new

是一个名为 newclass 方法,您从 Class 类继承了该方法。因此,您永远不会调用新方法,这意味着 @a = 1 永远不会被执行,这意味着 @a 始终未定义,这意味着它将始终评估为 nil 这意味着 self@aother@a 将始终为相同,这意味着 m 将始终为 true

您可能想要做的是提供一个构造函数,但 Ruby 没有构造函数。 Ruby 仅使用工厂方法。

真正想要重写的方法是实例方法initialize。现在您可能会问自己:“为什么我必须重写名为 initialize实例方法,而实际上我正在调用名为新的?”

Ruby 中的对象构造是这样工作的:对象构造分为两个阶段,分配初始化。分配是通过名为 allocate 的公共类方法完成的,该方法被定义为类 Class 的实例方法,并且通常从不被重写。它只是为对象分配了内存空间并设置了一些指针,但是此时该对象还不能真正使用。

这就是初始化器的用武之地:它是一个名为 initialize 的实例方法,它设置对象的内部状态并将其带入一致的、完全定义的状态,可供其他对象使用。

所以,为了完全创建一个新对象,你需要做的是这样的:

x = X.allocate
x.initialize

[注意:Objective-C 程序员可能会认识到这一点。]

但是,因为太容易忘记调用 initialize作为一般规则,对象在构造后应该完全有效,有一个名为 Class#new 的便捷工厂方法,它可以为您完成所有工作,看起来像这样:

class Class
  def new(*args, &block)
    obj = alloc
    obj.initialize(*args, &block)

    return obj
  end
end

[注意:实际上, initialize 是私有的,因此必须使用反射来规避访问限制,如下所示: obj.send(:initialize, *args, &block)]

最后,让我解释一下你的 m 方法出了什么问题。 (其他人已经解释了如何解决它。)

在 Ruby 中,没有办法(注意:在 Ruby 中,“没有办法”实际上翻译为“总有一种涉及反射的方法”)从实例之外。这就是为什么它被称为实例变量,因为它属于实例。这是 Smalltalk 的遗产:在 Smalltalk 中没有可见性限制,所有方法都是公共的。因此,实例变量是 Smalltalk 中唯一的封装方式,毕竟封装是 OO 的支柱之一。在 Ruby 中,存在可见性限制(例如,正如我们在上面所看到的),因此并非绝对有必要因此而隐藏实例变量。然而,还有另一个原因:统一访问原则。

UAP 规定如何使用功能应该独立于该功能实现的方式。因此,访问一个特征应该总是相同的,即统一的。原因是该功能的作者可以自由地更改该功能的内部工作方式,而不会破坏该功能的用户。换句话说,它是基本的模块化。

这意味着,例如,获取集合的大小应该始终相同,无论大小是否存储在变量中、每次动态计算、第一次延迟计算然后存储在变量中、记忆中还是其他什么。听起来很明显,但是 Java 就犯了这个错误:

obj.size # stored in a field

obj.getSize() # computed

Ruby 则采取了简单的方法。在 Ruby 中,只有一种使用功能的方式:发送消息。由于只有一种方式,因此访问非常统一。

因此,长话短说:您根本无法访问另一个实例的实例变量。您只能通过消息发送与该实例交互。这意味着另一个对象必须要么为您提供一个方法(在本例中至少是受保护的可见性)来访问其实例变量,要么您必须违反该对象的封装(从而失去 Uniform通过使用反射(在本例中为 instance_variable_get)进行访问(增加耦合度并冒未来损坏的风险)。

这就是它的全部荣耀:

#!/usr/bin/env ruby

class X
  def initialize(a=1)
    @a = a
  end

  def m(other) 
    @a == other.a
  end

  protected

  attr_reader :a
end

require 'test/unit'
class TestX < Test::Unit::TestCase
  def test_that_m_evaluates_to_true_when_passed_two_empty_xs
    x, y = X.new, X.new
    assert x.m(y)
  end
  def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
    assert X.new('foo').m(X.new('foo'))
  end
end

或者:

class X
  def m(other) 
    @a == other.instance_variable_get(:@a)
  end
end

我想说,您选择这两者中的哪一个取决于个人品味。标准库中的 Set 类使用反射版本,尽管 it 使用 instance_eval 代替:(

class X
  def m(other) 
    @a == other.instance_eval { @a }
  end
end

我不知道为什么。也许 在编写 Set 时,instance_variable_get 根本不存在。到 2 月份,Ruby 就将迎来 17 岁生日了,stdlib 中的一些内容是很早的时候的。)

There have already been several good answers to your immediate problem, but I have noticed some other pieces of your code that warrant a comment. (Most of them trivial, though.)

Here's four trivial ones, all of them related to coding style:

  1. Indentation: you are mixing 4 spaces for indentation and 5 spaces. It is generally better to stick to just one style of indentation, and in Ruby that is generally 2 spaces.
  2. If a method doesn't take any parameters, it is customary to leave off the parantheses in the method definition.
  3. Likewise, if you send a message without arguments, the parantheses are left off.
  4. No whitespace after an opening paranthesis and before a closing one, except in blocks.

Anyway, that's just the small stuff. The big stuff is this:

def new
  @a = 1
end

This does not do what you think it does! This defines an instance method called X#new and not a class method called X.new!

What you are calling here:

x = X.new

is a class method called new, which you have inherited from the Class class. So, you never call your new method, which means @a = 1 never gets executed, which means @a is always undefined, which means it will always evaluate to nil which means the @a of self and the @a of other will always be the same which means m will always be true!

What you probably want to do is provide a constructor, except Ruby doesn't have constructors. Ruby only uses factory methods.

The method you really wanted to override is the instance method initialize. Now you are probably asking yourself: "why do I have to override an instance method called initialize when I'm actually calling a class method called new?"

Well, object construction in Ruby works like this: object construction is split into two phases, allocation and initialization. Allocation is done by a public class method called allocate, which is defined as an instance method of class Class and is generally never overriden. It just allocates the memory space for the object and sets up a few pointers, however, the object is not really usable at this point.

That's where the initializer comes in: it is an instance method called initialize, which sets up the object's internal state and brings it into a consistent, fully defined state which can be used by other objects.

So, in order to fully create a new object, what you need to do is this:

x = X.allocate
x.initialize

[Note: Objective-C programmers may recognize this.]

However, because it is too easy to forget to call initialize and as a general rule an object should be fully valid after construction, there is a convenience factory method called Class#new, which does all that work for you and looks something like this:

class Class
  def new(*args, &block)
    obj = alloc
    obj.initialize(*args, &block)

    return obj
  end
end

[Note: actually, initialize is private, so reflection has to be used to circumvent the access restrictions like this: obj.send(:initialize, *args, &block)]

Lastly, let me explain what's going wrong in your m method. (The others have already explained how to solve it.)

In Ruby, there is no way (note: in Ruby, "there is no way" actually translates to "there is always a way involving reflection") to access an instance variable from outside the instance. That's why it's called an instance variable after all, because it belongs to the instance. This is a legacy from Smalltalk: in Smalltalk there are no visibility restrictions, all methods are public. Thus, instance variables are the only way to do encapsulation in Smalltalk, and, after all, encapsulation is one of the pillars of OO. In Ruby, there are visibility restrictions (as we have seen above, for example), so it is not strictly necessary to hide instance variables for that reason. There is another reason, however: the Uniform Access Principle.

The UAP states that how to use a feature should be independent from how the feature is implemented. So, accessing a feature should always be the same, i.e. uniform. The reason for this is that the author of the feature is free to change how the feature works internally, without breaking the users of the feature. In other words, it's basic modularity.

This means for example that getting the size of a collection should always be the same, regardless of whether the size is stored in a variable, computed dynamically every time, lazily computed the first time and then stored in a variable, memoized or whatever. Sounds obvious, but e.g. Java gets this wrong:

obj.size # stored in a field

vs.

obj.getSize() # computed

Ruby takes the easy way out. In Ruby, there is only one way to use a feature: sending a message. Since there is only one way, access is trivially uniform.

So, to make a long story short: you simply can't access another instance's instance variable. you can only interact with that instance via message sending. Which means that the other object has to either provide you with a method (in this case at least of protected visibility) to access its instance variable, or you have to violate that object's encapsulation (and thus lose Uniform Access, increase coupling and risk future breakage) by using reflection (in this case instance_variable_get).

Here it is, in all its glory:

#!/usr/bin/env ruby

class X
  def initialize(a=1)
    @a = a
  end

  def m(other) 
    @a == other.a
  end

  protected

  attr_reader :a
end

require 'test/unit'
class TestX < Test::Unit::TestCase
  def test_that_m_evaluates_to_true_when_passed_two_empty_xs
    x, y = X.new, X.new
    assert x.m(y)
  end
  def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
    assert X.new('foo').m(X.new('foo'))
  end
end

Or alternatively:

class X
  def m(other) 
    @a == other.instance_variable_get(:@a)
  end
end

Which one of those two you chose is a matter of personly taste, I would say. The Set class in the standard library uses the reflection version, although it uses instance_eval instead:

class X
  def m(other) 
    @a == other.instance_eval { @a }
  end
end

(I have no idea why. Maybe instance_variable_get simply didn't exist when Set was written. Ruby is going to be 17 years old in February, some of the stuff in the stdlib is from the very early days.)

乖不如嘢 2024-08-19 23:25:59

Getter有以下几种方法

class X
  attr_reader :a
  def m( other )
    a == other.a
  end
end

instance_eval:

class X
  def m( other )
    @a == other.instance_eval { @a }
  end
end

instance_variable_get:

class X
  def m( other )
    @a == other.instance_variable_get :@a
  end
end

我不认为 ruby​​ 有“friend”或“protected”访问的概念,甚至“private”也很容易到处乱砍。使用 getter 创建只读属性,instance_eval 意味着你必须知道实例变量的名称,因此内涵类似。

There are several methods

Getter:

class X
  attr_reader :a
  def m( other )
    a == other.a
  end
end

instance_eval:

class X
  def m( other )
    @a == other.instance_eval { @a }
  end
end

instance_variable_get:

class X
  def m( other )
    @a == other.instance_variable_get :@a
  end
end

I don't think ruby has a concept of "friend" or "protected" access, and even "private" is easily hacked around. Using a getter creates a read-only property, and instance_eval means you have to know the name of the instance variable, so the connotation is similar.

你不是我要的菜∠ 2024-08-19 23:25:59

如果您不使用 instance_eval 选项(如 @jleedev 发布的那样),并选择使用 getter 方法,您仍然可以对其进行保护

如果您想要 Ruby 中的 protected 方法,只需执行以下操作来创建只能从同一类的对象中读取的 getter:

class X
    def new()
        @a = 1
    end
    def m( other ) 
        @a == other.a
    end

    protected
    def a 
      @a
    end
end

x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a      # Throws error

If you don't use the instance_eval option (as @jleedev posted), and choose to use a getter method, you can still keep it protected

If you want a protected method in Ruby, just do the following to create a getter that can only be read from objects of the same class:

class X
    def new()
        @a = 1
    end
    def m( other ) 
        @a == other.a
    end

    protected
    def a 
      @a
    end
end

x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a      # Throws error
北笙凉宸 2024-08-19 23:25:59

不确定,但这可能会有所帮助:

在课堂之外,这有点困难:

# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
        from (irb):9

# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
    => []

http:// /whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility

Not sure, but this might help:

Outside of the class, it's a little bit harder:

# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
        from (irb):9

# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
    => []

http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文