Ruby:禁止更新作为类变量的数组

时间:2016-04-30 21:31:11

标签: arrays ruby class

我正在写一个简单的Tic Tac Toe游戏,其中我有一个用于棋盘,玩家,计算机和游戏本身的课程。在我的Board类中,我将一个类变量@board(它是一个数组)设置为attr_reader,它应该禁止直接写入它。虽然以下内容不起作用(按预期)

game_board = Board.new 
game_board.board = "some junk"

以下工作,我不想发生

game_board = Board.new
game_board.board[0] = "some junk"

如何停止将类数组变量@board写入?目前的类代码如下:

class Board
  attr_reader :board

  def initialize
    create_board
  end

  private
  def create_board
    @board = Array.new(3).map{Array.new(3)}
  end
end

game_board = Board.new 
game_board.board
 #=> [[nil,nil,nil],[nil,nil,nil],[nil,nil,nil]]
game_board.board = "junk"
 #=> undefined method 'board ='  

game_board.board[0] = "junk" 
game_board.board 
 #=> ["junk",[nil,nil,nil],[nil,nil,nil]] #I don't want to allow this!

我尝试使用谷歌搜索,但无济于事,但我是一个完整的初学者,所以我可能没有使用正确的搜索词

2 个答案:

答案 0 :(得分:7)

我相信你需要使数组不可变。

您可以使用Array#freeze来实现这一目标。

之后的代码应如下所示:

class Board
  attr_reader :board

  def initialize
    create_board
  end

  private
  def create_board
    @board = Array.new(3).map{Array.new(3).freeze}.freeze
  end
end

在运行你的第一个例子时:

>> game_board = Board.new 
#<Board:0x00000001648b50 @board=[[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]>
>> game_board.board = "some junk"
NoMethodError: undefined method `board=' for #<Board:0x00000001648b50>
    from (irb):14
    from /home/alfie/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'

在运行第二个例子时:

>> game_board = Board.new
#<Board:0x00000001639e48 @board=[[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]>
>> game_board.board[0] = "some junk"
RuntimeError: can't modify frozen Array
    from (irb):16
    from /home/alfie/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'

答案 1 :(得分:6)

仅在不attr_reader的情况下定义attr_writer,将仅阻止分配@board变量。换句话说,您的Board类不公开界面来修改@board中存储的内容,但不会阻止修改初始值。

您可以使用freeze

def create_board
  @board = Array.new(3) { Array.new(3).freeze }
  @board.freeze
end

(另外,你不需要map

冻结顶级数组和嵌套数组会做你所描述的,但我想它也会打破你的游戏,因为修改是完全不可能的。

我建议不要公开@board并将其视为私有。然后,您应该公开一个接口来设置电路板中的值,并提供一种方法来返回电路板的只读表示。

class Board

  def initialize
    create_board
  end

  def []=(x, y, value)
    @board[x][y] = value
  end

  def board
    @board.map { |a| a.dup.freeze }.freeze
  end

  private

  def create_board
    @board = Array.new(3) { Array.new(3) }
  end
end


b = Board.new
b.board
# => [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]

b[1,2] = "x"
b[0,0] = "o"
b.board
# => [["o", nil, nil], [nil, nil, "x"], [nil, nil, nil]]

b.board[0] = "junk"
# RuntimeError: can't modify frozen Array
b.board[0][1] = "junk"
# RuntimeError: can't modify frozen Array
b.board
# => [["o", nil, nil], [nil, nil, "x"], [nil, nil, nil]]