Статический блок в рубине - PullRequest
4 голосов
/ 07 декабря 2009

Некоторое время я был программистом на Java, и некоторое время пытаюсь переключиться на ruby. Я просто пытался разработать небольшую тестовую программу на ruby, и я собираюсь сделать что-то вроде следующего.

  1. Я хочу создать простой тип связанного списка объекта в ruby; где переменная экземпляра в классе указывает на другой экземпляр того же типа.
  2. Я хочу заполнить и связать все узлы; перед вызовом конструктора и только один раз. То, что мы обычно делаем в статическом блоке Java.

  3. Метод Initialize - это сигнатура конструктора в ruby. Есть ли вокруг них какие-то правила? Как и в Java, вы не можете вызвать другой конструктор из конструктора, если это не первая строка (или после вызова кода класса?)

Спасибо за помощь. -Priyank

Ответы [ 4 ]

19 голосов
/ 07 декабря 2009

Я хочу создать простой тип связанного списка объекта в 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

для простого связанного списка?

2 голосов
/ 07 декабря 2009

Вы можете просто инициализировать свои переменные класса в теле класса вне любого объявления метода. Он будет вести себя как статический инициализатор в Java:

class Klass
   @@foo = "bar"

   def sayFoo
      puts @@foo
   end

   def self.sayFoo
      puts @@foo
   end

 end

Поле класса @@foo здесь инициализируется как "bar".

0 голосов
/ 15 сентября 2011

Это в основном тот же самый ответ, который парадигматический дал в '09.

Здесь я хочу проиллюстрировать, что «статический инициализатор» может вызывать другой код. Я моделирую сценарий загрузки специального пользователя один раз при инициализации класса.

class Foo
  def user
    "Thomas"
  end
end

class Bar
  @@my_user = Foo.new.user

  def my_statically_defined_user
    @@my_user
  end
end

b = Bar.new
puts b.my_statically_defined_user   # ==> Thomas
0 голосов
/ 07 декабря 2009

В ruby ​​создание объектов работает следующим образом

class Class
  def new(*args)
    obj= self.allocate # get some memory
    obj.send(:initialize) # call the private method initialize
  end
end
  • Object#initialize - это обычный частный метод.
  • Если вы не хотите, чтобы что-то происходило до Object#initialize, вы должны написать свой Class#new. Но я не вижу причин, почему вы хотели бы сделать это.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...