所以我有多个 Thing 的子类,在这里表示为 ThingA 和 ThingB。
这里有几件事应该是理所当然的:
-
一个事物从来不是直接创建的——即
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的东西A。
如前所述,我有非常相似的代码,可以根据需要运行,而无需使用throw
或任何东西 - 我不知何故破坏了。
使用 return
只会阻止第一次初始化结束,并且仍然返回原始对象。
我不一定提倡这是设计系统的正确方法,但有两个原因导致你写的东西没有按照你的预期工作。
首先,即使在"简单"的情况下,上述操作也永远不会导致返回值为 ThingB;initialize
方法的最后一行是puts
调用,puts
始终具有 nil
的返回值,因此在"正常"方法的简单情况下,您的返回值仍然不会是 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
:
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
。
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
你正在寻找的东西已经存在了一段时间,被称为工厂模式(参见维基百科文章 https://en.wikipedia.org/wiki/Factory_method_pattern(:
"在基于类的编程中,工厂方法模式是一种创建模式,它使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类。
如果在创建之前不知道实例的类,则不应使用构造函数来创建它。 相反,您应该使用另一种方法来执行测试并创建相应类的实例。
在稳定、严格控制的类层次结构中,此方法可以是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
。
但是,如果您确实确定隐藏此区别不会混淆和阻碍您的用户,则在这种情况下,此策略可能是可以接受的。