在实例化时将类转换为子类

时间:2010-07-30 17:42:39

标签: ruby oop factory-pattern

我正在编写一个用于查询Mediawiki API的框架。我有一个Page类代表维基上的文章,我还有一个Category类,其中是-a Page,具有更具体的方法(比如能够计算该类别中的成员数量。我还有一个方法Page#category?,它通过查询API来确定实例化的Page对象是否实际代表了Mediawiki类别页面确定文章的命名空间。

class Page
  def initialize(title)
    # do initialization stuff
  end

  def category?
    # query the API to get the namespace of the page and then...
    namespace == CATEGORY_NAMESPACE
  end
end

class Category < Page
  # ...
end

我想要做的是能够检测我的框架的用户是否尝试使用Page对象(即Page.new("Category:My Category"))实例化Mediawiki类别,如果是,则实例化{{1} } object,而不是Category对象,直接来自Page构造函数。

在我看来,这应该是可能的,因为它让人想起Rails中的单表继承,但我不知道如何让它工作。

3 个答案:

答案 0 :(得分:6)

好的,有几件事:

您无法将类A的实例转换为A的子类B的实例。至少,不是自动的。 B可以(并且通常会)包含A中不存在的属性,它可以具有完全不同的构造函数等。因此,AFAIK,没有OO语言将允许您以这种方式“转换”类。

即使在静态类型语言中,当您实例化B,然后将其分配给a类型的变量A时,它仍然是B的实例,它没有被转换为它的祖先类。

Ruby是一种具有强大反射功能的动态语言,因此您可以随时决定在运行时实例化哪个类 - 请查看:

puts "Which class to instantiate: "
class_name = gets.chomp
klass = Module.const_get class_name
instance = klass.new

所以,这里不需要任何转换 - 只需首先实例化你需要的类。

另一件事:正如我在评论中提到的,方法category?完全错误,因为它违反了OOP原则。在Ruby中,您可以 - 并且应该 - 使用方法is_a?,因此您的检查将如下所示:

if instance.is_a? Category
  puts 'Yes, yes, it is a category!'
else
  puts "Nope, it's something else."
end

这只是冰山一角,还有更多关于实例化不同类的内容,而我在评论中链接的另一个问题可能是一个很好的起点,尽管有些代码示例可能会让您感到困惑。但绝对值得了解它们。

编辑:重新阅读更新后的问题后,在我看来,正确的方法是创建一个工厂类,让它检测并实例化不同的页面类型。因此,用户不会直接调用Page.new,而是调用类似

的内容
MediaWikiClient.get_page "Category:My Category"

get_page方法将实例化相应的类。

答案 1 :(得分:2)

为什么不是这样的?能够做到这一点就足够了!

class Page
  def self.new(title)
    if self == Page and is_category?(title)
      Category.new(title)
    else
      super
    end
  end

  def self.is_category?(title)
    # ... (query the API etc.)
  end

  def initialize(title)
    # do initialization stuff
  end

  def category?
    # query the API to get the namespace of the page and then...
    namespace == CATEGORY_NAMESPACE
  end
end

class Category < Page
  # ...
end

答案 2 :(得分:1)

您可以定义一个实例化类并返回实例的方法。 这被称为Factory Pattern

class PageFactory
  def create(title) # the pattern uses "create".. but "new" is more Ruby' style
    namespace = title[/\A[^:]+(?=:)/]
     # matches the prefix, up to and excluding the first colon.
    if namespace == CATEGORY_NAMESPACE
      Category.new(title)
    else
      Page.new(title)
    end
  end
end

class ClientClass
  def do_something()
    factory = PageFactory.new
    my_page = factory.create('Category:Foo') 
    my_page.do_something()
  end
end