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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在实例化时将类转换为子类相关的知识,希望对你有一定的参考价值。

我正在编写一个查询Mediawiki API的框架。我有一个Page类,代表维基上的文章,我也有一个Category类,这是一个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中的单表继承,但我不知道如何让它工作。

答案

好的,有几件事:

您无法将类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方法将实例化相应的类。

另一答案

为什么不这样的?能够做到这一点是一个足够的理由去做!

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
另一答案

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

以上是关于在实例化时将类转换为子类的主要内容,如果未能解决你的问题,请参考以下文章

类加载器实例化时的顺序

在 Python 中实例化时选择子类

在逻辑上表示单个值时将类转换为结构,类似于原始类型

如何在实例化时将值插入 C# 字典?

导出到 excel 时将类“pandas.tslib.Timedelta”转换为字符串

当子类被实例化时,超类的私有成员是不是也被实例化? [复制]