从Ruby中的超类初始化方法返回不同子类的对象

时间:2016-05-19 00:20:06

标签: ruby oop initialization return

所以我有多个Thing的子类,在这里表示为ThingA和ThingB 这里有一些事情应该被视为理所当然:

  • a Thing永远不会直接创建 - 即Thing.new

  • ThingA必须在初始化时通过测试,否则它应该是ThingB

  • 可以安全地假设ThingB是ThingB

这是我的层次结构的草图:

class Thing
  def initialize( var = 'yes' )
    @var = var    
    if !self.verify?
      ThingB.new( var )
    elsif self.class != ThingB
      #code for ThingA
      @Aness = 'huge' 
    end
    #code for ThingA & ThingB
    puts 'END'
  end

  def verify?
    if self.class == ThingA
      @var == 'yes'
    else
      true
    end
  end
end

class ThingA < Thing
end

class ThingB < Thing
end

我的问题是,我怎样才能获得

ThingA.new( 'no' )

要返回ThingB吗?

这真让我烦恼,因为我使用了非常类似的代码,但不知怎的,我失去了所需的功能。有了上面的内容,我得到以下输出:

[21] pry(main)> ThingA.new
END
=> #<ThingA:0x60bd4b0 @Aness="huge", @var="yes">
#this is fine
[22] pry(main)> ThingB.new
END
=> #<ThingB:0x53ba6b8 @var="yes">
#this also
[23] pry(main)> ThingA.new( 'no' )
END
END
=> #<ThingA:0x64bec40 @var="no">
#this should be ThingB

'END'打印两次,暗示ThingB已初始化,但不会返回以代替原来的ThingA。相反,我有一个没有Aness的ThingA。

如前所述,我有非常相似的代码,无需使用throw或其他任何东西 - 我以某种方式打破了。

使用return仅停止第一次初始化,并仍然返回原始对象。

3 个答案:

答案 0 :(得分:3)

我并不一定主张这是设计系统的正确方式,但有两个原因导致您所写的内容并不存在。像你想象的那样工作。

首先,即使在一个简单的&#39;情况,上述情况永远不会导致返回值为ThingB; initialize方法的最后一行是puts来电,而puts的返回值始终为nil,所以在正常的情况下,正常&{ #39;方法,您的返回值仍然不是ThingB实例,它是nil

但是,正如你所说,

  

使用return仅停止第一次初始化,并仍然返回原始对象。

我假设您的意思是在初始化方法中使用明确的return,就像这个假设的代码一样:

class Thing
  def initialize( var = 'yes' )
    @var = var    
    if !self.verify?
      return ThingB.new( var ) # explicit return
    elsif self.class != ThingB
      #code for ThingA
      @Aness = 'huge' 
    end
    #code for ThingA & ThingB
    puts 'END'
  end

  def verify?
    if self.class == ThingA
      @var == 'yes'
    else
      true
    end
  end
end

那为什么没有那么有用呢?答案很微妙,但最终很简单,也是理解Ruby的关键(我认为):你没有在代码中调用initialize,而是在调用new。 New不能返回任何初始化返回值,因为那时你的原始类定义(没有显式返回)会使ThingA.new返回nil![*]

new实际运作的方式更像是这样:

class Thing
  def self.new(*args)
    obj = self.allocate
    obj.initialize(*args) # sort of; initialize is private
    return obj
  end
end

您会注意到initialize的返回值完全被忽略;这是一件好事,如果我们不必让每个初始化程序都繁琐地返回self,并且每次忘记时我们都会收到错误。

因此,如果您希望ThingA.new返回ThingB的实例,则不需要修改ThingA#initialize,您需要修改ThingA.new:< / p>

class Thing
end

class ThingA < Thing
  def self.verify?(var)
    var == 'yes'
  end

  def self.new(var = 'yes')
    if self.verify?(var)
      super
    else
      ThingB.new(var)
    end
  end

  def initialize(var)
    @Aness = 'huge'
  end
end

class ThingB < Thing
end

我应该强调,这对你的代码来说不一定是明智的。但我认为知道如何去做,以及为什么它有效,对于理解Ruby非常重要。

[*]:同样,不是因为它缺少显式返回,而是因为它隐式返回最后一个计算表达式的值puts 'END',而puts总是返回nil }。

答案 1 :(得分:1)

class ThingB
  def initialize(var = "yes")
    @var = var
    puts "END"
  end
end

class ThingA < ThingB
  def initialize(var = "yes")
    #code for ThingA
    @Aness = "huge"
    super
  end
  class <<self
    alias old_new new
    def new(var = "yes")
      verify?(var) ? ThingA.old_new(var) : ThingB.new(var)
    end
    def verify?(var)
      var == "yes"
    end
  end
end

答案 2 :(得分:1)

您正在寻找的东西已经存在了一段时间,被称为工厂模式(请参阅https://en.wikipedia.org/wiki/Factory_method_pattern上的维基百科文章):

&#34;在基于类的编程中,工厂方法模式是一种创建模式,它使用工厂方法来处理创建对象的问题,而无需指定将要创建的对象的确切类。&# 34;

如果在创建之前未知实例的类,您不应该使用构造函数来创建它。您应该使用另一个执行测试的方法并创建相应类的实例。

在稳定,严格控制的类层次结构中,此方法可以是Thing类的类方法:

class Thing
  def self.create
    if something
      Thing1.new
    else
      Thing2.new
    end
  end
end

这会创建一个更通用的类对它的更具体的特化的依赖,这通常不是一个好主意,但如果你完全控制子类可能不会太糟糕。

如果存在循环依赖性问题(例如在定义Thing2之前使用它),那么您可以在定义Thing,Thing1和Thing2后创建最小的工厂类或模块:

require 'thing'
require 'thing1'
require 'thing2'

class ThingFactory
  def self.create
    # ...same logic as before
  end
end

另一种方法是在其他地方创建另一个方法(也就是说,在一个不同的,不相关的类中)来做同样的事情。

关于你对新手使用的REPL的评论,你可以做的很简单的事情来实现这个目的;您可以创建一个包含new方法的模块:

module Thing
  def self.new
    some_condition ? Thing1.new : Thing2.new
  end
end

在一般编程中,这是一个非常坏主意,因为它对使用您的代码的开发人员来说是非常误导的; Thing看起来像是一个使用传统new方法进行实例化的课程,但事实并非如此。

更好的方法是要求您的用户了解在这种情况下他们需要拨打ThingFactory.create而不是ThingX.new

但是,如果您确实确保隐藏此区别不会混淆和阻碍您的用户,则在这种情况下此策略可能是可接受的。