Рубиновые частные и публичные средства доступа
При определении аксессоров в 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
;)