Ruby OOP课程职责

时间:2016-01-06 22:05:56

标签: ruby oop

我仍在尝试用Ruby包围OOP。假设我正在尝试创建一个简单的Hangman游戏,我想从文本文件中选择一个随机单词。到目前为止,我在代码块中有两个例子。第一个示例显示了Word和Game类,其中Word类生成随机单词,Game类在initialize方法中调用Word类。第二个例子只有一个Game类,其中Game类本身生成随机单词。我的问题是,游戏类是否有责任生成随机单词或使用Word类?

# First Example
module Hangman

  class Word
    def self.words
      File.readlines("../words.txt")
    end

    def self.random
      words.select { |word| word.length > 4 && word.length < 13 }.sample
    end
  end

  class Game
    attr_reader :random_word

    def initialize
      @random_word = Hangman::Word.random
    end
  end

end

# Second Example
module Hangman

  class Game
    attr_reader :words, :random_word

    def initialize
      @words = File.readlines("../words.txt")
      @random_word = @words.select { |word| word.length > 4 && word.length < 13 }.sample
    end
  end

end

2 个答案:

答案 0 :(得分:0)

一般来说,这取决于。

对于这个特定情况,我认为大多数人会同意在Word和游戏之间拆分结构是一个好主意。

Word是一个很好的小可测试部分,所以它确实值得拥有自己的类。

它也可以在许多需要随机单词的游戏中重复使用。

我认为如果你重写单词使其具有初始化方法,这将变得更加清晰。然后游戏只是调用Word.new(...)来获得一个新的随机单词。

想象一下,如果有一个名为“单词”的宝石已经完成了所有这些。你会很高兴添加宝石并说完成交易。嗯,这是一种简单的方法,告诉你已经做了很好的分工,即使没有这样的宝石存在。

顺便说一下,一旦你认为这应该是一个单独的类,你可能想检查是否有人已经为你做了。在这种情况下,有一个宝石随机词。

单词初始化的参数是什么?那么这个词的长度,技能水平等等。

class Word
  def self.words
    @words ||= File.readlines("../words.txt")
  end

  def initialize(min_length, max_length)
    Word.words.select do |word| 
      word.length > length && word.length < max_length 
    end.sample
  end
end

答案 1 :(得分:0)

桑迪·梅斯(Sandi Metz)在Practical Object Oriented Design in Ruby.中有一个很好的例子来回答这个问题,可悲的是,由于它是受版权保护的,因此我无法直接链接到该段落。

在她的示例中,尽管由于自行车在问题领域中的明显存在,自行车似乎是该类别的一个很好的候选者,但在她的应用程序开发中,她需要计算传动比,但她意识到该计算的输入都只与齿轮有关:两个Gear实例中每个实例的齿数,因此将功能置于Gear上,从而推迟了Bicycle类的创建,直到稍后。

因此,通常的答案是:找出所需计算的输入值,并将该计算的定义放在具有最大这些输入值作为类的类上。

在您的特定情况下:

  

生成随机单词或使用Word类是Game类的责任吗?

首先,看来您的Word类更像是WordList类,尽管根据您的未来发展方向,它可能仍然是Word类,但在composite pattern的体现中。如果确实将其保留为WordList类,则它没有实例方法,因此讨论该类的职责将变得非常困难。实际上,自身类具有方法,但是始终希望该类位于“单例实例化范围”或常量中。 Ruby类名是常量,因此仅在常量级别定义方法是有效的过程性代码,而不是面向对象的代码。

要使WordList面向对象,您可以将IO实例(文件是一个子类,但是为什么要依赖其类不需要您的代码定义的方法的子类?)传递给WordList#initialize,从而可能通过以下方式提供单例访问:

def self.singleton_instance
  @singleton_instance ||= new(File.open("../words.txt"))
end

这允许其他客户端通过提供任何类型的IO(包括StringIO)在其他上下文中重用WordList类,并分离并明确表明加载默认的单例WordList只是此类期望的一种方式要使用,需要父目录中的恒定作用域级别的文件,并允许定义WordList的实例级别的行为。

到目前为止,您似乎需要从所有单词中随机选择一个实例级行为。回到Sandi Metz的建议,WordList 确实似乎是放置随机选择计算的好地方,因为WordList会有一个字段:

attr_reader :words

def initialize(io)
  @words = io.readlines
end

并且正是要过滤的 words 字段,因此此类是该功能的理想选择:

def random # notice no self. prefix
  words.select { |word| word.length > 4 && word.length < 13 }.sample
end

以及后来的实际使用情况

@random_word = Hangman::WordList.singleton_instance.random

如果您以后需要的话,这也为您提供了一个位置,可以将单例实例换成另一个实例,而无需更改WordList类。那也应该为遵守Open Closed Principle而得分。

(顺便说一句:对于方法名称,“随机”似乎不是一个好的选择-它不是 just 随机的,而且限制在4到13之间(不包括)。也许” random_suitable_length_word“?)