Ваш обновленный вопрос теперь выглядит совсем иначе.Если я вас правильно понимаю, вы хотите подключиться к размещению и инициализации объектов, что абсолютно не имеет ничего общего с метаклассами.(Но вы все еще не пишите, что вы на самом деле хотите сделать, поэтому я все еще могу отключиться.)
В некоторых объектно-ориентированных языках объекты создаются конструкторами,Однако в Ruby нет конструкторов.Конструкторы - просто фабричные методы (с глупыми ограничениями);нет смысла использовать их в хорошо разработанном языке, если вместо этого вы можете использовать (более мощный) метод фабрики.
Построение объекта в Ruby работает следующим образом: построение объекта разбивается на две фазы, выделение и инициализация .Распределение выполняется с помощью общедоступного метода класса allocate
, который определяется как метод экземпляра класса Class
и обычно никогда не переопределяется .(На самом деле, я не думаю, что вы на самом деле можете переопределить его.) Он просто выделяет пространство памяти для объекта и устанавливает несколько указателей, однако объект на самом деле не пригоден на данный момент.
Вот где приходит инициализатор: это метод экземпляра initialize
, который устанавливает внутреннее состояние объекта и переводит его в согласованное, полностью определенное состояние, которое может использоваться другими объектами.
Итак, чтобы полностью создать новый объект, вам нужно сделать следующее:
x = X.allocate
x.initialize
[Примечание: программисты Objective-C могут это распознать.]
Однако, поскольку слишком легко забыть вызвать initialize
и, как правило, объект должен быть полностью действительным после построения, существует удобный фабричный метод, называемый Class#new
, который выполняет всю эту работу за вас и выглядит примерно так:this:
class Class
def new(*args, &block)
obj = allocate
obj.initialize(*args, &block)
return obj
end
end
[Примечание: на самом деле initialize
является частным, поэтому необходимо использовать отражение, чтобы обойти ограничения доступа следующим образом: obj.send(:initialize, *args, &block)
]
То естьКстати, для создания объекта вы вызываете метод открытого класса Foo.new
, но вы реализуете метод частного экземпляра Foo#initialize
, который, кажется, сильно сбивает с толкуновичков.
Однако, нет этого ни в коей мере не запекается в язык.Тот факт, что метод первичной фабрики для любого класса обычно называется new
, является просто соглашением (и иногда мне хотелось бы, чтобы он был другим, потому что он похож на конструкторы в Java, но полностью отличается).В других языках конструктор должен иметь определенное имя.В Java оно должно иметь то же имя, что и класс, что означает, что а) может быть только один конструктор и б) анонимные классы не могут иметь конструкторов, потому что у них нет имен.В Python фабричный метод должен называться __new__
, что снова означает, что может быть только один.(Как в Java, так и в Python вы, конечно, можете иметь разные фабричные методы, но их вызов выглядит иначе, чем вызов по умолчанию, в то время как в Ruby (и Smalltalk, откуда возник этот шаблон) он выглядит точно так же.)
В Ruby может быть сколько угодно фабричных методов с любым именем, а фабричный метод может иметь много разных имен.(Например, для классов коллекций метод фабрики часто имеет псевдоним []
, что позволяет писать List[1, 2, 3]
вместо List.new(1, 2, 3)
, что в конечном итоге выглядит больше как массив, подчеркивая тем самым природу списков коллекции.)
Короче говоря:
- стандартизированный фабричный метод -
Foo.new
, но это может быть что угодно Foo.new
вызывает allocate
для выделения памяти дляпустой объект foo
Foo.new
затем вызывает foo.initialize
, то есть Foo#initialize
метод экземпляра - , все три из которых являются просто методами, как любой другой, который вы можете отменить,переопределить, переопределить, перенести, псевдоним и еще много чего еще
- хорошо, за исключением
allocate
, который должен выделять память во время выполнения Ruby, что вы не можете сделать из Ruby
В Python __new__
примерно соответствует new
и allocate
в Ruby, а __init__
точно соответствует initialize
в Ruby. Основное отличие состоит в том, что в Ruby new
вызывает initialize
, тогда как в Python runtime автоматически вызывает __init__
после __new__
.
Например, вот класс, который допускает создание максимум 2 экземпляров:
class Foo
def self.new(*args, &block)
@instances ||= 0
raise 'Too many instances!' if @instances >= 2
obj = allocate
obj.send(:initialize, *args, &block)
@instances += 1
return obj
end
attr_reader :name
def initialize(name)
@name = name
end
end
one = Foo.new('#1')
two = Foo.new('#2')
puts two.name # => #2
three = Foo.new('#3') # => RuntimeError: Too many instances!