Рубиновые частные и публичные средства доступа

При определении аксессоров в Ruby может быть противоречие между краткостью (которую мы все любим) и лучшей практикой.

Например, если я хочу представить значение в экземпляре, но запретить его обновление какими-либо внешними объектами, я могу сделать следующее:

class Pancake
  attr_reader :has_sauce

  def initialize(toppings)
    sauces = [:maple, :butterscotch]
    @has_sauce = toppings.size != (toppings - sauces).size
...

Но вдруг я использую необработанную переменную экземпляра, которая заставляет меня дергаться. Я имею в виду, что если бы мне нужно было обработать has_sauce перед установкой в ​​будущем, мне, возможно, нужно было бы сделать намного больше рефакторинга, чем просто переопределить метод доступа. И давай, сырые переменные экземпляра? Blech.

Я мог бы просто проигнорировать проблему и использовать attr_accessor, Я имею в виду, что любой может установить атрибут, если он действительно хочет; это ведь Руби. Но потом я теряю идею инкапсуляции данных, интерфейс объекта менее четко определен, а система потенциально намного более хаотична.

Другое решение было бы определить пару аксессоров под разными модификаторами доступа:

class Pancake
  attr_reader :has_sauce
  private
    attr_writer :has_sauce
  public

  def initialize(toppings)
    sauces = [:maple, :butterscotch]
    self.has_sauce = toppings.size != (toppings - sauces).size
  end
end

Который выполняет свою работу, но это кусок стандартного образца для простого средства доступа и, откровенно говоря, эээ.

Так есть ли лучший, более рубиновый способ?

5 ответов

Решение

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

class << Object
  def private_accessor(*names)
    names.each do |name|
      attr_accessor name
      private "#{name}="
    end
  end
end

Тогда используйте private_accessor как бы вы attr_accessor (Я думаю, что вам нужно лучшее имя, чем private_accessor, хотя)

private можно взять символ арг, так что...

class Pancake
  attr_accessor :has_sauce
  private :has_sauce=
end

или же

class Pancake
  attr_reader :has_sauce
  attr_writer :has_sauce; private :has_sauce=
end

так далее...

Но что случилось с "сырыми" переменными экземпляра? Они являются внутренними для вашего случая; единственный код, который будет называть их по имени, это код внутри pancake.rb который все твое. Тот факт, что они начинают с @что, я полагаю, заставило вас сказать "блех", это то, что делает их личными. Думать о @ как сокращение для private если хочешь.

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

Вы можете поставить attr_reader в private область действия, например:

      class School
  def initialize(students)
    @students = students
  end

  def size
    students.size
  end

  private

  attr_reader :students
end

School.new([1, 2, 3]).students

Это вызовет ошибку, как и ожидалось:

      private method `students' called for #<School:0x00007fcc56932d60 @students=[1, 2, 3]> (NoMethodError)

Ruby 3.0 сделал модификаторы доступа и attr_*работать друг с другом , так что вы можете просто написать

      private attr_reader :has_sauce

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

В этом конкретном примере это может помочь распознать, что toppings скорее всего, это атрибут, который вы хотите сохранить для других целей, и has_sauce является "виртуальным атрибутом", характеристикой модели, которая зависит от базового атрибута toppings.

Примерно так может показаться чище:

class Pancake
  def initialize(toppings)
    @toppings = toppings
  end

  def has_sauce?
    sauces = [:maple, :butterscotch]
    (@toppings & sauces).any?
  end
end

Решать вам или нет attr_accessor :toppings также. Если вы просто выбрасываете начинки, ваш класс менее Pancake и больше PancakeToppingDetector;)

Другие вопросы по тегам