Ruby:如何防止通过属性读取器修改数组实例变量

时间:2021-11-07 11:06:51

sorry for this noob question... let's say we have :

很抱歉这个问题……假设我们有:

class TestMe
 attr_reader :array

 def initialize
   @array = (1..10).to_a
 end

end

结束

it is then possible to do :

这样就有可能做到:

>> a = TestMe.new
=> #<TestMe:0x00000005567228 @x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>
>> a.array.map! &:to_s
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
>> a.array
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
  • this clearly goes against encapsulation, doesn'it ?
  • 这显然不利于封装,不是吗?
  • is there any way to quickly protect the array variable from being changed ?
  • 有什么方法可以快速保护数组变量不被修改吗?
  • ... or do i need to implement a deep-copy reader every time my instance variable has "destructive" methods ?
  • …还是每次实例变量具有“破坏性”方法时都需要实现深度拷贝阅读器?

EDIT i read somewhere it is "bad OO" to expose an array instance variable. If it's true, why ?

编辑我读到的某个地方显示数组实例变量是“坏OO”。如果这是真的,为什么?

7 个解决方案

#1


11  

You cannot do much with attr_reader, because attr_reader :array generates the following code:

你不能对attr_reader做太多,因为attr_reader:array生成以下代码:

def array; @array; end

If you don't want to expose array instance, you can return Enumerator of this array (external iterator). Enumerator is a good iterator abstraction and does not allow you to modify original array.

如果不想公开数组实例,可以返回该数组的枚举数(外部迭代器)。枚举器是一个很好的迭代器抽象,不允许修改原始数组。

def array; @array.to_enum; end

What good for encapsulation and what not depends on the abstraction your class presents. Generally this is not good for encapsulation to expose internal state of an object, including internal array. You may want to expose some methods that operate on the @array instead of exposing @array (or even its iterator) itself. Sometimes this is fine to expose array - always look at the abstraction your class presents.

什么有利于封装,什么不取决于类呈现的抽象。通常,这不利于封装以公开对象的内部状态,包括内部数组。您可能希望公开一些对@array操作的方法,而不是公开@array(甚至是它的迭代器)本身。有时,公开数组也很好——总是查看类所呈现的抽象。

#2


5  

How about returning a copy of the original array from getter:

如何从getter中返回原始数组的副本:

class TestMe

  attr_writer :array

  def initialize
    @array = (1..10).to_a
  end

  def array
    @array.dup
  end

end

In that case you can't directly modify original array but with attribute writer you can replace it with the new one (if you need).

在这种情况下,您不能直接修改原始数组,而是使用属性写入器,您可以将其替换为新的数组(如果需要的话)。

#3


1  

Any instance can become immutable by calling freeze on it:

任何实例都可以通过调用freeze来变得不可变:

class TestMe
 attr_reader :array

 def initialize
   @array = (1..10).to_a
   @array.freeze
 end
end

a = TestMe.new
a.array << 11
# Error: can't modify frozen array

#4


1  

If you want the array to remain mutable, but not when returned through the reader, then don't return the array, but just a wrapper which exposes "safe" methods.

如果您希望数组保持可变,但不是通过读取器返回时,那么不要返回数组,而是要返回一个包装器,该包装器公开“安全”方法。

require 'forwardable'
class SafeArray
  extend Forwardable
  def initialize(array); @array = array; end
  # add the other methods you want to expose to the following line
  def_delegators :@array, :size, :each, :[], :map
end

class TestMe
  def initialize
    @array = (1..10).to_a
  end
  def array
    @wrapper ||= SafeArray.new(@array)
  end
end

#5


1  

Creating a specific attribute reader method that copies the original attribute works well, but keep in mind that neither @array.dup nor Array.new(@array) will perform a deep copy. Which means that if you have an array of arrays (like [[1, 2], [3, 4]]), none of the array's values will be protected against changes. To perform a deepcopy in ruby, the easiest way I found was this :

创建一个复制原始属性的特定属性读取器方法很好,但是请记住,这两个都不是@array。dup或Array.new(@array)将执行深度拷贝。这意味着如果您有一个数组数组(比如[[1,2],[3,4]),那么数组的值都不会受到更改的保护。要在ruby中执行深度拷贝,我发现最简单的方法是:

return Marshal.load( Marshal.dump(@array) )

Marshall.dump transforms any object into a string that can later be decoded to yield the object back (the process is called serializing). This way, you get a deepcopy of the given object. Easy but a bit dirty I have to admit.

马歇尔。dump将任何对象转换为字符串,这些字符串稍后可以解码以返回对象(这个过程称为序列化)。这样,您将获得给定对象的深度副本。容易,但我得承认有点脏。

#6


0  

This is against encapsulation, but we can solve the problem by properly tuning the getter method of that attribute.

这是针对封装的,但是我们可以通过正确调优该属性的getter方法来解决这个问题。

class TestMe

 def initialize
   @array = (1..10).to_a
 end

 def array
   Array.new(@array)
 end

end

#7


0  

You encapsulate by creating a method with the same name as your instance variable but make it end with equals sign. In your example that would be:

您可以通过创建与实例变量同名的方法进行封装,但以等号结尾。在你的例子中是:

def array=
..
end

In that method you do whatever it is that you want to do before assigning new values to the array

在该方法中,在为数组分配新值之前,您可以做任何您想做的事情

#1


11  

You cannot do much with attr_reader, because attr_reader :array generates the following code:

你不能对attr_reader做太多,因为attr_reader:array生成以下代码:

def array; @array; end

If you don't want to expose array instance, you can return Enumerator of this array (external iterator). Enumerator is a good iterator abstraction and does not allow you to modify original array.

如果不想公开数组实例,可以返回该数组的枚举数(外部迭代器)。枚举器是一个很好的迭代器抽象,不允许修改原始数组。

def array; @array.to_enum; end

What good for encapsulation and what not depends on the abstraction your class presents. Generally this is not good for encapsulation to expose internal state of an object, including internal array. You may want to expose some methods that operate on the @array instead of exposing @array (or even its iterator) itself. Sometimes this is fine to expose array - always look at the abstraction your class presents.

什么有利于封装,什么不取决于类呈现的抽象。通常,这不利于封装以公开对象的内部状态,包括内部数组。您可能希望公开一些对@array操作的方法,而不是公开@array(甚至是它的迭代器)本身。有时,公开数组也很好——总是查看类所呈现的抽象。

#2


5  

How about returning a copy of the original array from getter:

如何从getter中返回原始数组的副本:

class TestMe

  attr_writer :array

  def initialize
    @array = (1..10).to_a
  end

  def array
    @array.dup
  end

end

In that case you can't directly modify original array but with attribute writer you can replace it with the new one (if you need).

在这种情况下,您不能直接修改原始数组,而是使用属性写入器,您可以将其替换为新的数组(如果需要的话)。

#3


1  

Any instance can become immutable by calling freeze on it:

任何实例都可以通过调用freeze来变得不可变:

class TestMe
 attr_reader :array

 def initialize
   @array = (1..10).to_a
   @array.freeze
 end
end

a = TestMe.new
a.array << 11
# Error: can't modify frozen array

#4


1  

If you want the array to remain mutable, but not when returned through the reader, then don't return the array, but just a wrapper which exposes "safe" methods.

如果您希望数组保持可变,但不是通过读取器返回时,那么不要返回数组,而是要返回一个包装器,该包装器公开“安全”方法。

require 'forwardable'
class SafeArray
  extend Forwardable
  def initialize(array); @array = array; end
  # add the other methods you want to expose to the following line
  def_delegators :@array, :size, :each, :[], :map
end

class TestMe
  def initialize
    @array = (1..10).to_a
  end
  def array
    @wrapper ||= SafeArray.new(@array)
  end
end

#5


1  

Creating a specific attribute reader method that copies the original attribute works well, but keep in mind that neither @array.dup nor Array.new(@array) will perform a deep copy. Which means that if you have an array of arrays (like [[1, 2], [3, 4]]), none of the array's values will be protected against changes. To perform a deepcopy in ruby, the easiest way I found was this :

创建一个复制原始属性的特定属性读取器方法很好,但是请记住,这两个都不是@array。dup或Array.new(@array)将执行深度拷贝。这意味着如果您有一个数组数组(比如[[1,2],[3,4]),那么数组的值都不会受到更改的保护。要在ruby中执行深度拷贝,我发现最简单的方法是:

return Marshal.load( Marshal.dump(@array) )

Marshall.dump transforms any object into a string that can later be decoded to yield the object back (the process is called serializing). This way, you get a deepcopy of the given object. Easy but a bit dirty I have to admit.

马歇尔。dump将任何对象转换为字符串,这些字符串稍后可以解码以返回对象(这个过程称为序列化)。这样,您将获得给定对象的深度副本。容易,但我得承认有点脏。

#6


0  

This is against encapsulation, but we can solve the problem by properly tuning the getter method of that attribute.

这是针对封装的,但是我们可以通过正确调优该属性的getter方法来解决这个问题。

class TestMe

 def initialize
   @array = (1..10).to_a
 end

 def array
   Array.new(@array)
 end

end

#7


0  

You encapsulate by creating a method with the same name as your instance variable but make it end with equals sign. In your example that would be:

您可以通过创建与实例变量同名的方法进行封装,但以等号结尾。在你的例子中是:

def array=
..
end

In that method you do whatever it is that you want to do before assigning new values to the array

在该方法中,在为数组分配新值之前,您可以做任何您想做的事情