Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: PriyankОтветов: 4

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

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

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

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

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

rubyconstructor

Ответы

Ответ 1 · рейтинг 19 · Jörg W Mittag

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

Небольшое замечание: слово type - очень опасное слово в Ruby, особенно если вы пришли с Java. Из-за исторической случайности это слово используется как в динамической, так и в статической типизации для обозначения двух лишь внешне связанных, но очень разных вещей.

В динамической типизации тип - это метка, которая прикрепляется к значению (не ссылка).

Также в Ruby понятие типа гораздо шире, чем в Java. В сознании Java-программиста "тип" означает то же самое, что и "класс" (хотя это не так, поскольку интерфейсы и примитивы также являются типами). В Ruby "тип" означает "что я могу с ним сделать".

Пример: в Java, когда я говорю, что что-то имеет тип String, я имею в виду, что это прямой экземпляр класса String. В Ruby, когда я говорю, что что-то имеет тип String, я имею в виду либо

  • прямой экземпляр класса String или
  • экземпляр подкласса класса String или
  • объект, который отвечает на метод #to_str или
  • объект, который ведет себя неотличимо от String.

В Ruby все исполняемое. В частности, не существует такого понятия, как "объявление класса": тело класса - это просто исполняемый код, как и любой другой. Если у вас есть список определений методов в теле вашего класса, это не объявления, которые читаются компилятором и затем превращаются в объект класса. Это выражения, которые выполняются оценщиком одно за другим.

Таким образом, вы можете поместить любой код в тело класса, и этот код будет оцениваться при создании класса. В контексте тела класса self привязано к классу (помните, классы - это такие же объекты, как и любые другие).

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

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

Ответ 2 · рейтинг 3 · paradigmatic

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

class Klass
   @@foo = "bar"

   def sayFoo
      puts @@foo
   end

   def self.sayFoo
      puts @@foo
   end

 end

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

Ответ 3 · johannes

В 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. Но я не вижу причин, по которым вы хотели бы это сделать.

Ответ 4 · Purplejacket

По сути, это тот же ответ, который 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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы