【问题标题】:Convert a class to a subclass on instantiation在实例化时将类转换为子类
【发布时间】:2018-03-20 19:29:53
【问题描述】:

我正在编写一个用于查询Mediawiki API 的框架。我有一个 Page 类,它代表 wiki 上的文章,我还有一个 Category 类,它 is-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 类别,如果是,则实例化 Category 对象,而不是Page 对象,直接来自 Page 构造函数。

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

【问题讨论】:

  • Page 中的 category? 方法意味着祖先类知道子类,这在 OOP 方面似乎不是一个好主意。另外,你能澄清一下你的最后一句话吗?
  • @Mladen:不确定 Ruby 语法,但他似乎想要Category foo = new Page() 而不是Page bar = new Category()
  • 那么他可能应该看看stackoverflow.com/questions/746207/…,一般来说,谷歌一下工厂模式。
  • 抱歉,我想在我下班之前发布一个问题并且离开电脑 10 个小时是个坏主意:p 为了清楚起见,我将编辑我的问题。
  • @Mladen @Brian 我希望现在更清楚:)

标签: ruby oop factory-pattern


【解决方案1】:

好的,有几件事:

您不能转换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 方法会实例化相应的类。

【讨论】:

  • 我试图在我的编辑中使其更清晰,但Page#category? 没有检查对象是什么类型的类,它使用页面的信息来确定它属于哪个命名空间。我不知道我还能把它放在哪里......
  • 无论如何,我想我对工厂模式并不是很熟悉,但这似乎是正确的方法,谢谢。
  • 啊,抱歉,我没有意识到您在谈论 WikiMedia 命名空间,而不是 Ruby 命名空间! :) 无论如何,检查 WM 命名空间并相应地实例化适当类的地方将是工厂。当然,您可以将工厂方法放入您的 Page 类中,再看看另一个问题 - Brian Campbell 在那里给出了很好的答案和代码示例。
  • "AFAIK,没有 OO 语言允许您以这种方式“转换”类"哦,总是有例外! :-) 在这种情况下,CLOS 有CHANGE-CLASS 就是这样做的。有一些通用函数可以用来控制它的工作原理。
【解决方案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

【讨论】:

    【解决方案3】:

    您可以定义一个实例化类并返回实例的方法。 这被称为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
    

    【讨论】:

    • 这行不通。 PageFactory 中的new 方法是一个实例方法,您正在调用doSomething 中的类方法。此外,重新定义new 方法也不是一个好主意,因为它用于实例化已调用的类。顺便说一句,Ruby 约定是使用下划线分隔的方法名,而不是驼峰式。
    猜你喜欢
    • 2010-10-30
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    相关资源
    最近更新 更多