Метод класса vs константа в Ruby/Rails

Я реализовал форму, содержащую стробированную выпадающую таблицу для коллекции, и мне было интересно, что будет лучшим решением, я знаю, что оба способа, описанные ниже, работают, но я сделал следующее:

class Example

  # Options for Example.
  self.options
    [ 'Yes', 'No', 'Not sure' ]
  end
end

который вызывается Example.options, но я знаю, что можно сделать следующее:

class Example

  # Options for Example.
  OPTIONS = [ 'Yes', 'No', 'Not sure' ]
end

который будет вызываться с помощью Example::OPTIONS.

Вопрос в том, является ли любой из них хорошим способом или это просто не имеет значения вообще?

Ответ 1

Последнее лучше. Если бы это был метод, новый массив и новые строки будут создаваться каждый раз, когда он вызывается, что является пустой тратой ресурсов.

Ответ 2

TL; DR: Это зависит. Используются ли значения вне класса? Могут ли они стать динамичными? Могут ли они измениться для подклассов?

Как писал @sawa, недостаток метода (написанный таким образом) заключается в том, что каждый новый массив и строки создаются каждый раз.

Лучший способ написать это:

class Example
  def self.options
    @options ||= ['Yes', 'No', 'Not sure']
  end
end

Массив хранится в переменной экземпляра @options, чтобы избежать создания нового массива каждый раз.

Написанный таким образом метод очень похож на константу.

Одно ключевое различие заключается в том, что если Example является подклассом, более естественно уточнить метод options, чем константа options:

class Parent < Example
  def self.options
    @options ||= [*super, 'Extra']
  end
end

Сделать что-то подобное с константами сложно. Представьте, что ваш список параметров используется в методе класса, это будет выглядеть так:

class Example
  OPTIONS = ['Yes', 'No', 'Not sure']

  def self.foo(arg)
     puts "Available options:",
          self::OPTIONS  # The self:: is needed here
     # ...
  end
end

class Parent < Example
  OPTIONS = [*superclass::OPTIONS, 'Extra']
end

Трудная вещь о константах состоит в том, что self::OPTIONS и options не всегда одинаковы, а self.options и options одинаковы. Константы обычно используются без указания области (например, options вместо self::OPTIONS), и наследование просто не будет работать в этом случае.

Обратите внимание, что этот метод дает возможность сделать динамический результат (т.е. возвращать разные результаты в зависимости от других обстоятельств) без изменения API.

Заключительное примечание: я бы рекомендовал вызывать freeze в вашем массиве, чтобы никто не изменял его.

Ответ 3

Что я обычно делаю, так это сочетание вышеупомянутых методов:

class Player
  JURISDICTIONS = %i(de uk ru)

    def self.jurisdictions
        JURISDICTIONS
    end
end

У него есть несколько преимуществ:

  • Он обеспечивает чистый интерфейс, инкапсулируя константу (вы вызываете Player.jurisdictions вместо Player::JURISDICTIONS).
  • Дополнительную логику можно добавить позже, просто изменив метод.
  • Метод может быть пропущен в тестах.

IMHO, производительность здесь не имеет значения.

Обновление: Константа может быть скрыта с помощью метода private_constant (http://ruby-doc.org/core-2.3.0/Module.html#method-i-private_constant)

Ответ 4

Чтобы уточнить предложение Артура, я бы пошел с переменной класса, чтобы скрыть видимость константы.

class Player
  @@jurisdictions = %i(de uk ru)

  def self.jurisdictions
    @@jurisdictions
  end
end