Когда я должен использовать Struct vs. OpenStruct?

В целом, каковы преимущества и недостатки использования OpenStruct по сравнению с Struct? Какой тип общих вариантов использования подходит каждому из них?

Ответ 1

С помощью OpenStruct вы можете произвольно создавать атрибуты. A Struct, с другой стороны, должен иметь свои атрибуты, определенные при его создании. Выбор одного над другим должен основываться главным образом на том, нужно ли вам добавлять атрибуты позже.

Способ думать о них - это как промежуточная точка спектра между хэшами с одной стороны и классами - с другой. Они подразумевают более конкретную взаимосвязь между данными, чем a Hash, но у них нет методов экземпляра, как класс. Например, набор параметров для функции имеет смысл в хеше; они только слабо связаны. Имя, адрес электронной почты и номер телефона, необходимые для функции, могут быть упакованы вместе в Struct или OpenStruct. Если это имя, адрес электронной почты и номер телефона нуждаются в методах предоставления имени в форматах "Первый Последний" и "Последний, Первый", тогда вы должны создать класс для его обработки.

Ответ 2

Другой ориентир:

require 'benchmark'
require 'ostruct'

REP = 100000

User = Struct.new(:name, :age)

USER = "User".freeze
AGE = 21
HASH = {:name => USER, :age => AGE}.freeze

Benchmark.bm 20 do |x|
  x.report 'OpenStruct slow' do
    REP.times do |index|
       OpenStruct.new(:name => "User", :age => 21)
    end
  end

  x.report 'OpenStruct fast' do
    REP.times do |index|
       OpenStruct.new(HASH)
    end
  end

  x.report 'Struct slow' do
    REP.times do |index|
       User.new("User", 21)
    end
  end

  x.report 'Struct fast' do
    REP.times do |index|
       User.new(USER, AGE)
    end
  end
end

Для нетерпеливого, который хочет получить представление о результатах тестов, не запуская их самостоятельно, вот вывод кода выше (на MB Pro 2.4GHz i7)

                          user     system      total        real
OpenStruct slow       4.430000   0.250000   4.680000 (  4.683851)
OpenStruct fast       4.380000   0.270000   4.650000 (  4.649809)
Struct slow           0.090000   0.000000   0.090000 (  0.094136)
Struct fast           0.080000   0.000000   0.080000 (  0.078940)

Ответ 3

UPDATE:

Начиная с Ruby 2.4.1 OpenStruct и Struct намного ближе к скорости. См. fooobar.com/questions/39137/...

РАНЕЕ:

Для полноты: Struct и Класс против Хеш против OpenStruct

Запуск аналогичного кода как Burtlo's, на Ruby 1.9.2, (1 из 4 ядер x86_64, 8GB RAM) [таблица отредактирована для выравнивания столбцов]:

creating 1 Mio Structs :         1.43 sec ,  219 MB /  90MB (virt/res)
creating 1 Mio Class instances : 1.43 sec ,  219 MB /  90MB (virt/res)
creating 1 Mio Hashes  :         4.46 sec ,  493 MB / 364MB (virt/res)
creating 1 Mio OpenStructs :   415.13 sec , 2464 MB / 2.3GB (virt/res) # ~100x slower than Hashes
creating 100K OpenStructs :     10.96 sec ,  369 MB / 242MB (virt/res)

OpenStructs sloooooow и с интенсивной памятью, а недостаточно масштабируются для больших наборов данных

Создание 1 Mio OpenStructs ~ 100x медленнее, чем, создавая 1 Mio Хеши.

start = Time.now

collection = (1..10**6).collect do |i|
  {:name => "User" , :age => 21}
end; 1

stop = Time.now

puts "#{stop - start} seconds elapsed"

Ответ 4

Варианты использования для этих двух приложений совершенно разные.

Вы можете представить класс Struct в Ruby 1.9 как эквивалент объявления struct в C. В Ruby Struct.new берет набор имен полей в качестве аргументов и возвращает новый класс. Аналогично, в C объявление struct принимает набор полей и позволяет программисту использовать новый сложный тип, как и любой встроенный тип.

Ruby:

Newtype = Struct.new(:data1, :data2)
n = Newtype.new

С

typedef struct {
  int data1;
  char data2;
} newtype;

newtype n;

Класс OpenStruct можно сравнить с объявлением анонимной структуры в C. Он позволяет программисту создать экземпляр сложного типа.

Ruby:

o = OpenStruct.new(data1: 0, data2: 0) 
o.data1 = 1
o.data2 = 2

С

struct {
  int data1;
  char data2;
} o;

o.data1 = 1;
o.data2 = 2;

Вот несколько распространенных случаев использования.

OpenStructs можно использовать для простого преобразования хэшей в одноразовые объекты, которые отвечают на все хеш-ключи.

h = { a: 1, b: 2 }
o = OpenStruct.new(h)
o.a = 1
o.b = 2

Структуры могут быть полезны для определения сокращенных классов.

class MyClass < Struct.new(:a,:b,:c)
end

m = MyClass.new
m.a = 1

Ответ 5

OpenStructs использует значительно больший объем памяти и работает медленнее по сравнению с Structs.

require 'ostruct' 

collection = (1..100000).collect do |index|
   OpenStruct.new(:name => "User", :age => 21)
end

В моей системе следующий код выполнялся за 14 секунд и потреблял 1,5 ГБ памяти. Ваш пробег может отличаться:

User = Struct.new(:name, :age)

collection = (1..100000).collect do |index|
   User.new("User",21)
end

Это закончилось почти мгновенно и потребляло 26,6 МБ памяти.

Ответ 6

Посмотрите API на новый метод. Много различий можно найти там.

Лично мне очень нравится OpenStruct, так как мне не нужно заранее определять структуру объекта и просто добавлять вещи, как я хочу. Я полагаю, что это будет его главное преимущество (?)?

Ответ 7

Используя код @Robert, я добавляю Hashie:: Mash к эталонному элементу и получил этот результат:

                           user     system      total        real
Hashie::Mash slow      3.600000   0.000000   3.600000 (  3.755142)
Hashie::Mash fast      3.000000   0.000000   3.000000 (  3.318067)
OpenStruct slow       11.200000   0.010000  11.210000 ( 12.095004)
OpenStruct fast       10.900000   0.000000  10.900000 ( 12.669553)
Struct slow            0.370000   0.000000   0.370000 (  0.470550)
Struct fast            0.140000   0.000000   0.140000 (  0.145161)

Ответ 8

Struct:

>> s = Struct.new(:a, :b).new(1, 2)
=> #<struct a=1, b=2>
>> s.a
=> 1
>> s.b
=> 2
>> s.c
NoMethodError: undefined method `c` for #<struct a=1, b=2>

OpenStruct:

>> require 'ostruct'
=> true
>> os = OpenStruct.new(a: 1, b: 2)
=> #<OpenStruct a=1, b=2>
>> os.a
=> 1
>> os.b
=> 2
>> os.c
=> nil