我正在为一个对象创建一个类(比如,Bar
(,由另一个类(比如Foo#bar
(的方法返回,几乎MatchData
对象是由Regexp#match
返回的。
但是班级MatchData
没有.new
!
我知道我不需要模仿MatchData
实现,但我想理解它,并在发现它有趣时知道如何去做。假设我不希望客户端创建Bar
对象,除非通过调用 Foo#bar
.
问题:
- 在内部,如何在没有
.new
的情况下创建MatchData
对象? - 我该如何实现它(模仿或不模仿
MatchData
(?
MatchData.new
方法显式未定义:
rb_cMatch = rb_define_class("MatchData", rb_cObject);
rb_define_alloc_func(rb_cMatch, match_alloc);
rb_undef_method(CLASS_OF(rb_cMatch), "new"); // <- here
您可以通过undef_method
在纯Ruby中执行相同的操作:
class Bar
class << self
undef_method :new
end
def initialize
@bar = '123' # <- for demonstration purposes
end
end
尝试调用Bar.new
现在将导致错误:
Bar.new #=> undefined method `new' for Bar:Class (NoMethodError)
要创建一个没有new
方法的新实例,您可以手动调用allocate
(也可以调用initialize
(:
bar = Bar.allocate #=> #<Bar:0x007f9eba047cd8>
Bar.send(:initialize) #=> "123"
bar #=> #<Bar:0x007fd8e0847658 @bar="123">
(需要send
,因为initialize
是私有的(
让我从你不应该开始。不遗余力地约束用户做他们想做的事情,即使它不是公共接口,这也是不雅的。一种更惯用的方法是更明确地表明它不是公共接口的一部分。您可以通过将类设为私有来做到这一点:
class RegexMockery
class MatchDataMockery
def initialize(whatever)
puts "I'm being created #{whatever}"
end
def [](_)
'42'
end
end
private_constant :MatchDataMockery
def match(string)
MatchDataMockery.new(string)
end
end
match_result = RegexMockery.new.match('foo')
# I'm being created foo
# => #<RegexMockery::MatchDataMockery:0x007fe990de2ed0>
match_result[0] # => '42'
RegexMockery::MatchDataMockery # !> NameError: private constant RegexMockery::MatchDataMockery referenced
但是,如果您坚持人们讨厌您,请保存该方法,取消定义并在要创建实例时调用它:
class Foo
def initialize(whatever)
puts "Now you see me #{whatever}"
end
def brag
puts "I can create Foos and you can't!!!1!!"
end
end
class Bar
foos_new = Foo.method(:new)
Foo.singleton_class.send :undef_method, :new
define_method(:sorcery) do
foos_new.call('bar').brag
end
end
Bar.new.sorcery
# Now you see me bar
# I can create Foos and you can't!!!1!!
Foo.new # !> NoMethodError: undefined method `new' for Foo:Class