如何从外部访问ruby中的类变量?

11

我正试图从类外的一个方法中访问一个类变量。

这是我的类:

class Book

  @@bookCount = 0
  @@allBooks = []

  def self.allBooks
    @@allBooks
  end

  def self.bookCount
    @@bookCount
  end

  attr_accessor :name,:author,:date,:genre,:rating

  def initialize(name, author, date, genre, rating)
      @name = name
      @author = author
      @date = date
      @genre = genre
      @rating = rating
      @@bookCount += 1
      @@allBooks << self
  end

end

这是试图访问类变量@@bookCount的方法。

def seeBookShelf
  if @@bookCount == 0
    puts "Your bookshelf is empty."
  else
    puts "You have " + @bookCount + " books in your bookshelf:"
    puts allBooks
  end
end

当我尝试执行该方法时,我得到以下结果:

当我试图执行方法时,我会遇到这个问题:

undefined local variable or method `bookCount' for main:Object (NameError)

我怎样才能从外部访问bookCount变量?


1
第一个问题:为什么要使用类变量?这些变量在类和实例之间共享信息,往往会导致关注点混淆。self.class.allBooks比直接访问更好。 - tadman
3
Ruby 的惯例强烈建议您使用 see_book_shelf 这样的写法给变量或方法命名,而不要在它们中使用大写字母。类名则是混合大小写并以首字母大写为特点,就像您在这里看到的一样。 - tadman
1
由于这种方式保留了创建的任何Book实例的引用,因此基本上会导致内存问题。最好创建一个专门用于存储书籍的BookShelf类,而不是将这个责任委托给Book类级别。 - tadman
也许可以在Book类中定义VARIABLE = @@book_count,然后在类外使用Book::VARIABLE。但不确定这是否是推荐的技术,所以请谨慎使用。 - Sagar Pandya
@sagarpandya82 共享常量也是一件麻烦的事情。最好通过方法调用来建立一个薄层中介,这样你就有了一些隔离,并且方法的实现可以更改而不会破坏大量的东西。 - tadman
读者挑战:如果没有 @@bookCount 的 getter 方法,并且不允许使用方法 Module#class_variable_get,你该如何做到这一点? - Cary Swoveland
5个回答

15

使用class_variable_get方法在类外访问类变量:

class Foo
  @@a = 1
end

Foo.class_variable_get(:@@a)
=> 1

3
技术上来说是正确的,但并不明智。class_variable_get方法旨在用于反射目的,而非一般编程。 - tadman

3
您可以使用 class_eval 来在特定类的作用域内评估一段代码块:
class Book
  @@bookCount = 1
end

Book.class_eval '@@bookCount'
# => 1

仅仅为了好玩...你实际上可以使用 class_eval 进行各种技巧操作,例如在类中定义一个新的方法而不需要进行猴子补丁:

Book.class_eval { @@bookCount = 5 }
Book.class_eval '@@bookCount'
# => 5

Book.class_eval do
  def self.hey_look_a_new_method
    return "wow"
  end
end

Book.hey_look_a_new_method
# => "wow"

3

在大多数情况下,类实例变量优于类变量。当与继承一起使用时,后者容易出现各种奇怪的行为。

考虑以下代码:

class Book
  @book_count = 0
  @all_books = []

  class << self
    attr_reader :book_count
    attr_reader :all_books
  end

  # further code omitted.

end

使用这段代码,Book.book_count和Book.all_books将获得预期的数据。

1

你需要一个 getter 来访问类变量,尝试使用这段代码。 参见 http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ 获取解释。 最好使用字符串插值,否则会出现类型错误,并且更符合 Ruby 的风格。

class Book
  @@bookCount = 0

  def self.bookCount
    @@bookCount
  end
end

def seeBookShelf
  if Book.bookCount == 0
    puts "Your bookshelf is empty."
  else
    puts "You have #{Book.bookCount} books in your bookshelf:"
  end
end

seeBookShelf # Your bookshelf is empty.

几乎和@Gregory的答案一样。 - Ilya
是的,我们正在同时回答,我喜欢尝试我的答案并将它们作为一个完整的可运行代码发布,但往往会被更快的人打败。我认为我们的解决方案比Ilya的更好,你试过吗?注意到当bookcount> 0时的错误了吗? - peter

0

你必须指定变量的类别:

def seeBookShelf
  if Book.bookCount == 0
    puts "Your bookshelf is empty."
  else
    puts "You have " + Book.bookCount + " books in your bookshelf:"
    puts Book.allBooks
  end
end

然而,直接访问变量是一种不好的做法。你应该在你的类中添加getter方法。或者创建一个新的类BookShelf来存储你所有的书。 - user6225298
如果这是在实例方法中,self.class 也可以使用。 - tadman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接