Я хочу создать простой тип связанного списка объекта в ruby; где переменная экземпляра в классе указывает на другой экземпляр того же типа.
Просто небольшое замечание: слово type - очень опасное слово в Ruby, особенно если вы пришли из Java. Из-за исторического происшествия слово используется как в динамической типизации, так и в статической типизации для обозначения двух только внешне связанных, но очень разных вещей.
В динамической типизации тип - это метка, которая прикрепляется к значению ( не ссылка).
Кроме того, в Ruby понятие типа намного шире, чем в Java. По мнению программиста на Java, «тип» означает то же самое, что и «класс» (хотя это не так, поскольку интерфейсы и примитивы также являются типами). В Ruby «тип» означает «что с ним делать».
Пример: в Java, когда я говорю что-то типа String , я имею в виду, что это прямой экземпляр класса String
. В Ruby, когда я говорю что-то типа String , я имею в виду, что это либо
- прямой экземпляр класса
String
или
- экземпляр подкласса класса
String
или
- объект, который отвечает на метод
#to_str
или
- объект, который ведет себя неотличимо от строки.
Я хочу заполнить и связать все узлы; перед вызовом конструктора и только один раз. То, что мы обычно делаем в статическом блоке Java.
В 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
для cons-list в стиле 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
для простого связанного списка?