Статический блок в Ruby

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

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

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

Спасибо за помощь. -Приянк


person Priyank    schedule 07.12.2009    source источник
comment
Какой у Вас вопрос? Часть о правилах в инициализации? что ты уже испробовал? С какими проблемами вы столкнулись?   -  person Mike Woodhouse    schedule 07.12.2009


Ответы (4)


Я хочу создать простой тип связанного списка объекта в 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
comment
отличный ответ Спасибо! Дал мне много информации. - person Priyank; 07.12.2009

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

class Klass
   @@foo = "bar"

   def sayFoo
      puts @@foo
   end

   def self.sayFoo
      puts @@foo
   end

 end

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

person paradigmatic    schedule 07.12.2009
comment
Спрашивающий говорил об экземпляре, а не о переменных класса - person johannes; 07.12.2009
comment
Он говорил о статических блоках в java (см. заголовок и пункт 2). Они используются для инициализации переменных класса в java. - person paradigmatic; 08.12.2009
comment
Вопрос немного сбивает с толку, но этот ответ - то, что я искал. Спасибо! Я увидел, что также могу вызывать статические методы, такие как @@foo = static_method(), если этот метод объявлен до @@foo. - person Ferran Maylinch; 10.10.2020

В 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. Но я не вижу причин, по которым вы хотели бы это сделать.
person johannes    schedule 07.12.2009
comment
Я копаюсь на кладбище вопросов, но должен ли ваш def new быть def Class.new там? - person compman; 14.05.2011
comment
@compman: Нет, класс Foo является объектом класса Class, поэтому, когда вы вызываете Foo.new, вы вызываете метод Class#new. - person Andrew Grimm; 15.09.2011

По сути, это тот же ответ, который paradigmatic дал еще в 2009 году.

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

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
person Purplejacket    schedule 14.09.2011