所以我有多个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
仅停止第一次初始化,并仍然返回原始对象。
答案 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
。
但是,如果您确实确保隐藏此区别不会混淆和阻碍您的用户,则在这种情况下此策略可能是可接受的。