Ваш обновленный вопрос теперь выглядит совсем иначе. Если я вас правильно понял, вы хотите подключиться к распределению и инициализации объектов, что не имеет абсолютно ничего общего с метаклассами. (Но вы по-прежнему не пишете, что именно хотите делать, так что я все равно могу уйти.)
В некоторых объектно-ориентированных языках объекты создаются конструкторами. Однако в Ruby нет конструкторов. Конструкторы - это всего лишь фабричные методы (с дурацкими ограничениями); нет причин использовать их на хорошо разработанном языке, если вместо этого вы можете просто использовать (более мощный) фабричный метод.
Построение объекта в Ruby работает следующим образом: построение объекта делится на две фазы: выделение и инициализация. Распределение выполняется общедоступным методом класса с именем allocate
, который определяется как метод экземпляра класса Class
и обычно никогда не переопределяется. (На самом деле, я не думаю, что вы действительно можете переопределить его.) Он просто выделяет пространство памяти для объекта и устанавливает несколько указателей, однако объект на данный момент не может использоваться .
Здесь на помощь приходит инициализатор: это метод экземпляра с именем initialize
, который устанавливает внутреннее состояние объекта и переводит его в согласованное, полностью определенное состояние, которое может использоваться другими объектами.
Итак, чтобы полностью создать новый объект, вам нужно сделать следующее:
x = X.allocate
x.initialize
[Примечание: программисты Objective-C могут это понять.]
Однако, поскольку слишком легко забыть вызвать initialize
и, как правило, объект должен быть полностью действительным после построения, существует удобный фабричный метод под названием Class#new
, который делает все, что работает за вас, и выглядит примерно так:
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 среда выполнения автоматически вызывает __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!
person
Jörg W Mittag
schedule
20.04.2010