Ruby 私有实例变量,有例外

Posted

技术标签:

【中文标题】Ruby 私有实例变量,有例外【英文标题】:Ruby private instance variables, with exceptions 【发布时间】:2011-01-23 13:54:57 【问题描述】:

我正在用 ruby​​ 制作纸牌游戏。

我有 Game 类,它有一个 Player 对象数组。

 array_of_players = Array[
  Player.new("Ben"),
  Player.new("Adam"),
  Player.new("Peter"),
  Player.new("Fred"),
 ]
 my_game = Game.new(array_of_players)

 puts my_game.players[2].name #=> Peter

每个玩家也可以访问游戏,这样他们就可以像这样访问游戏的重要部分

self.game.last_card_dealt

每个玩家也有卡片(Player.cards),我想确保玩家不能访问彼此的卡片。但是,游戏确实需要访问卡牌,所以我认为使用private 不合适,而且玩家需要访问彼此的一些信息,所以我不希望使用private要么……

基本上,我希望这些工作。

self.cards #where self is a Player object
self.players[0].cards #where self is the Game
self.game.players[0].name #where self is a Player object

这失败了:

self.hand.players[0].cards  #=> Nice try sucker! Cheating is for losers.

如何处理像这样更复杂的权限? 谢谢。

【问题讨论】:

应用程序(甚至用户)角度的隐私概念不应与 OO 术语中的隐私混淆。我认为在这种情况下依赖 OO 隐私概念是完全错误的,您应该通过应用程序逻辑来确保这一点。 【参考方案1】:

感谢您的所有回复。

最后我想我可以给授权对象一个密钥,用于允许它访问方法的核心。

游戏对象有@auth_object并将其设置为它打算访问其秘密方法的玩家对象,玩家秘密方法检查hand.auth_object是否为self,否则它什么也不做。然后@auth_object 被设置回零。 @auth_object 有 attr_reader 但没有 writer。

这行得通。

【讨论】:

【参考方案2】:

这比我的其他答案更实用,并使用 Game 对象作为游戏本身中所有信息(玩家、卡片等)的代表。请注意,您仍然必须相信调用者会通过自己,但认真地划定界限在哪里?

class Player
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class Cards
  attr_accessor :cards
end

class Game
  attr_reader :name, :players

  def initialize(players)
    @name = "Game Master"
    @hands = []
    @players = players.each do |p|
      puts "Added %s to game." % p.name
      @hands << :player => p, :cards => Cards.new
    end
  end

  def view_hand(player, caller)
    @hands.each do |hand|
      if hand[:player] == player
        if hand[:player] == caller or caller == self
          puts "%s: You can access all these cards: %s" % [caller.name, hand[:cards]]
        else
          # Do something to only display limited cards depending on this caller's view capabilities
          puts "%s: You can only access the cards I will let you see: %s" % [caller.name, hand[:cards]]
        end
      end
    end
  end

  def my_cards(player)
    @hands.each do |hand|
      puts "%s's cards: %s" % [player.name, hand[:cards]] if hand[:player] == player
    end
  end

end

g = Game.new([Player.new('Bob'), Player.new('Ben')])

puts "\nCalling each Player's cards as each Player:\n\n"
g.players.each do |gp|
  g.players.each do |p|
    g.view_hand(gp, p)
  end
end

puts "\nCalling each Player's cards as Game:\n\n"
g.players.each do |p|
  g.view_hand(p, g)
end

puts "\nEach Player calls for their own cards:\n\n"
g.players.each do |p|
  g.my_cards(p)
end

输出:

    Added Bob to game.
    Added Ben to game.

    Calling each Player's cards as each Player:

    Bob: You can access all these cards: #<Cards:0x100121c58>
    Ben: You can only access the cards I will let you see: #<Cards:0x100121c58>
    Bob: You can only access the cards I will let you see: #<Cards:0x100121bb8>
    Ben: You can access all these cards: #<Cards:0x100121bb8>

    Calling each Player's cards as Game:

    Game Master: You can access all these cards: #<Cards:0x100121c58>
    Game Master: You can access all these cards: #<Cards:0x100121bb8>

    Each Player calls for their own cards:

    Bob's cards: #<Cards:0x100121c58>
    Ben's cards: #<Cards:0x100121bb8>

【讨论】:

【参考方案3】:

这玩起来很有趣。我不确定这是否是最好的答案,但它确实有效。关键是将调用对象传递给 Player.cards(obj),并检查它是 Player 本身还是 Game 类型,两者都具有合法访问权限。

class Player
  attr_accessor :name, :game
  attr_writer :cards

  def initialize(name)
    @name = name
    @game = nil
    @cards = nil
  end

  def cards(caller)
    puts "%s cards called by %s." % [self, caller]
    if caller.kind_of?(Game) or caller == self
      puts "Here's your cards %s." % @cards
    else
      puts "Nice try sucker!  Cheating is for losers."
    end
  end
end

class Cards
  def initialize
    @cards = [1, 2, 3]
  end
end

class Game
  attr_reader :players

  def initialize(players)
    @players = players.each do |p|
      puts "Added %s to game." % p.name
      p.game = self
      p.cards = Cards.new
    end
  end
end

g = Game.new([Player.new('Bob'), Player.new('Ben')])

puts "\nCalling each Player's cards as each Player:\n\n"
g.players.each do |gp|
  g.players.each do |p|
    p.cards(gp)
  end
end

puts "\nCalling each Player's cards as Game:\n\n"
g.players.each do |p|
  p.cards(g)
end

还有输出:

    Added Bob to game.
    Added Ben to game.

    Calling each Player's cards as each Player:

    #<Player:0x100122b30> cards called by #<Player:0x100122b30>.
    Here's your cards #<Cards:0x1001229c8>.
    #<Player:0x100122ae0> cards called by #<Player:0x100122b30>.
    Nice try sucker!  Cheating is for losers.
    #<Player:0x100122b30> cards called by #<Player:0x100122ae0>.
    Nice try sucker!  Cheating is for losers.
    #<Player:0x100122ae0> cards called by #<Player:0x100122ae0>.
    Here's your cards #<Cards:0x100122928>.

    Calling each Player's cards as Game:

    #<Player:0x100122b30> cards called by #<Game:0x100122ab8>.
    Here's your cards #<Cards:0x1001229c8>.
    #<Player:0x100122ae0> cards called by #<Game:0x100122ab8>.
    Here's your cards #<Cards:0x100122928>.

【讨论】:

感谢您的回复。我想知道如何阻止恶意玩家偷偷传入他们正在调用 .cards 的玩家的玩家对象?我们不是回到第一方吗?例如。 self.game.player[0].cards(self.game.player[0]) 首先我要说的是,我相信 Mladen 的上述评论完全适用于这种思路。不过,我也觉得把这些东西拼出来很有趣(也许这里的拼图是你的目标,而不是它的实用性?)。在这种情况下,我会说,您需要所有查询通过公共接口方法通过 Game(我们选择的委托人)进行路由,并根据调用者拥有的权限交回所有信息。 (即用户只能通过查询 Game 来获取游戏信息、自己的牌、对手的名字和对手可见的牌)。【参考方案4】:

保持Game.player 为私有,以禁止玩家通过数组访问其他玩家。

例如,当self 是玩家时,self.game.players[0].name 有点傻。

也许您想要一个只返回玩家姓名数组的公共 Game.player_names 方法?

除此之外,您还可以公开Players.opponents 方法。

示例

Game.player_names

class Game
  # ...
  def player_names
    self.players.collect  |p| p.name 
  end

  private
  # private game methods
end

玩家.对手

class Player
  # ...
  def opponents(i=nil)
    return i.nil? ? self.game.player_names : self.game.player_names[i]
  end
end

【讨论】:

以上是关于Ruby 私有实例变量,有例外的主要内容,如果未能解决你的问题,请参考以下文章

Ruby / IRB:将实例变量设置为私有或不可见?

Ruby 类实例变量与类变量

Ruby的attr_accessor如何产生类变量或类实例变量而不是实例变量?

Ruby:自动将实例变量设置为方法参数?

从子类访问父类的私有实例变量?

Ruby 元编程:动态实例变量名