Я хочу создать простой тип связанного списка объекта в ruby; где переменная экземпляра в классе указывает на другой экземпляр того же типа.
Небольшое замечание: слово type — очень опасное слово в Ruby, особенно если вы пришли с Java. Из-за исторической случайности это слово используется как в динамической, так и в статической типизации для обозначения двух лишь внешне связанных, но очень разных вещей.
В динамической типизации тип — это метка, которая прикрепляется к значению (не ссылка).
Также в Ruby понятие типа гораздо шире, чем в Java. В сознании Java-программиста "тип" означает то же самое, что и "класс" (хотя это не так, поскольку интерфейсы и примитивы также являются типами). В Ruby «тип» означает «что я могу с ним сделать».
Пример: в Java, когда я говорю, что что-то имеет тип String, я имею в виду, что это прямой экземпляр класса String
. В Ruby, когда я говорю, что что-то имеет тип String, я имею в виду либо
- прямой экземпляр класса
String
или
- экземпляр подкласса класса
String
или
- объект, который отвечает на метод
#to_str
или
- объект, который ведет себя неотличимо от String.
Я хочу заполнить и связать все узлы; перед вызовом конструктора и только один раз. Что-то, что мы обычно делаем в блоке Java Static.
В Ruby все исполняемое. В частности, не существует такого понятия, как «объявление класса»: тело класса — это просто исполняемый код, как и любой другой. Если у вас есть список определений методов в теле вашего класса, это не объявления, которые читаются компилятором и затем превращаются в объект класса. Это выражения, которые выполняются оценщиком одно за другим.
Таким образом, вы можете поместить любой код в тело класса, и этот код будет оцениваться при создании класса. В контексте тела класса self
привязано к классу (помните, классы — это такие же объекты, как и любые другие).
Метод Initialize — это сигнатура конструктора в ruby. Есть ли какие-то правила вокруг них? Как и в Java, вы не можете вызвать другой конструктор из конструктора, если это не первая строка (или после вызова кода класса?)
Ruby не имеет имеет конструкторов. Конструкторы — это просто фабричные методы (с дурацкими ограничениями); нет причин иметь их на хорошо разработанном языке, если вместо этого вы можете просто использовать (более мощный) фабричный метод.
Создание объекта в Ruby работает следующим образом: создание объекта разделено на две фазы: распределение и инициализация. Выделение выполняется общедоступным методом класса allocate
, который определяется как метод экземпляра класса Class
и обычно никогда не переопределяется. Он просто выделяет место в памяти для объекта и устанавливает несколько указателей, однако в данный момент объект на самом деле непригоден для использования.
Вот тут-то и появляется инициализатор: это метод экземпляра с именем initialize
, который устанавливает внутреннее состояние объекта и приводит его в непротиворечивое, полностью определенное состояние, которое может использоваться другими объектами.
Итак, чтобы полностью создать новый объект, вам нужно сделать следующее:
x = X.allocate
x.initialize
[Примечание: программисты Objective-C могут распознать это.]
Однако, поскольку слишком легко забыть вызвать initialize
, и, как правило, объект должен быть полностью корректным после создания, существует удобный фабричный метод Class#new
, который делает всю эту работу за вас и выглядит примерно так:
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
[Примечание: на самом деле initialize
является закрытым, поэтому для обхода ограничений доступа необходимо использовать отражение: obj.send(:initialize, *args, &block)
]
Это, кстати, причина, по которой при создании объекта вы вызываете метод общедоступного класса Foo.new
, но при этом реализуете частный метод экземпляра Foo#initialize
, который, похоже, сбивает с толку много новичков.
Чтобы ответить на ваш вопрос: поскольку метод инициализатора - это такой же метод, как и любой другой, нет абсолютно никаких ограничений относительно того, что вы можете делать с инициализатором, в частности, вы можете вызывать super
когда угодно, где угодно и как часто вы хотите.
Кстати: поскольку initialize
и new
— это обычные методы, нет причин, по которым их нужно называть initialize
и new
. Это всего лишь условность, хотя и довольно строгая, поскольку она воплощена в базовой библиотеке. В вашем случае вы хотите написать класс-коллекцию, а класс-коллекция обычно предлагает альтернативный фабричный метод с именем []
, чтобы я мог вызывать List[1, 2, 3]
вместо List.new(1, 2, 3)
.
В качестве примечания: очевидное преимущество использования обычных методов для создания объектов заключается в том, что вы можете создавать экземпляры анонимных классов. Это невозможно в Java по абсолютно никакой разумной причине. Единственная причина, по которой это не работает, заключается в том, что конструктор имеет то же имя, что и класс, а анонимные классы не имеют имени, следовательно, не может быть конструктора.
Хотя я не совсем уверен, зачем вам нужно что-то запускать перед созданием объекта. Если я что-то упустил, не должен ли список в основном быть
class List
def initialize(head=nil, *tail)
@head = head
@tail = List.new(*tail) unless tail.empty?
end
end
для списка минусов в стиле Lisp или
class List
def initialize(*elems)
elems.map! {|el| Element.new(el)}
elems.zip(elems.drop(1)) {|prv, nxt| prv.instance_variable_set(:@next, nxt)}
@head = elems.first
end
class Element
def initialize(this)
@this = this
end
end
end
для простого связанного списка?
person
Jörg W Mittag
schedule
07.12.2009