2014-08-29 3 views
4

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

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

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 

Который получает работу, но это кусок шаблонного для простого аксессору и довольно откровенно: РЭБ.

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

+0

** Что делает работу ** Неверно! has_sauce внутри метода initialize() - это локальная переменная, а не переменная экземпляра. Вы даже не проверили свой код (который также имеет другую ошибку). ** Я имею в виду, если мне нужно было обработать has_sauce перед настройкой на будущую дату, мне было бы нужно сделать гораздо больше рефакторинга, чем просто переопределить аксессуар. ** Установка переменной экземпляра путем перехода через сеттер - хорошая практика, и использование двух модификаторов доступа - это способ выполнения этого для переменной экземпляра только для чтения. – 7stud

+0

Самые глубокие извинения за непроверенный код и хорошо заметны. Исправлено сейчас, fwiw. Кроме того, я рад, что вы согласны с тем, что это хорошая практика, но эта реализация выглядит слегка взломанной. Наверное, только я. –

ответ

3

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)

+0

Это больше похоже на это! Я рассматривал возможность делать именно это, но я хотел изучить все другие варианты, прежде чем изменять Object ... Я стараюсь не вмешиваться в основы языка, если это не будет очень хакировано и/или неэффективно, чтобы этого не сделать .. Или, если я действительно этого не хочу. ;) –

+0

Кроме того, я не мог придумать хорошее имя для аксессуаров! Возможно, поэтому это не язык. Я нашел это в своих путешествиях: https://github.com/dbrady/scrapbin/blob/master/scraps/scoped_attr_accessor.rb, который имеет преимущество в большом наборе тестов, но недостатком является требование, чтобы два аксессуаров составляли требуемый результат. –

+0

** и я действительно разделяю ваше мнение ** Действительно? Итак, для вашей чувствительности написания: 'private attr_writer: has_sauce' подобен ногтям на доске, но пишет' class << Object def private_accessor (* names) names.each do | name | имя attr_accessor частное "# {имя} =" конец конец конец private_accessor: has_sauce' чувствует себя гладкой, как шелк? – 7stud

2

private может взять символ ARG, так что ...

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, который является вашим. Тот факт, что они начинаются с @, который, как я полагаю, заставил вас сказать «blech», делает их частными. Подумайте о @ как стенографию для private, если хотите.

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

+0

Я объяснил, что мне не нравится в использовании переменных экземпляра в этом контексте. Но для дальнейшего объяснения этого, принцип, который я использую: Если у вас есть аксессор, используйте аксессуар, а не необработанную переменную или механизм, который он скрывает. Необработанной переменной является реализация, аксессор - это интерфейс. Должен ли я игнорировать интерфейс, потому что я являюсь внутренним для экземпляра? Я бы не стал, если бы это был attr_accessor. –

+0

Но спасибо за ваш интерес. –

+1

Затем 'attr_accessor: bar; private: bar =; 'то, что вы хотите. – AlexChaffee

0

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

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

Что-то подобное может чувствовать себя чище:

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

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

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

+0

Я вижу «виртуальный атрибут» как то, что мы вынуждены реализовать при использовании фреймворков, ORM и т. П. Что-то, что позволяет нам вводить наш код на путь любого метапрограммирования для нас. В простой PORO, как это, я не вижу, как это имеет смысл; это всего лишь метод. :) –

+0

Что касается всего «использования переменных экземпляра на всем протяжении кода», я укажу вам на следующее: https://github.com/dbrady/scoped_attr_accessor , где кто-то выполнил эту работу для меня. :) –

+0

Кроме того, Санди Мец упоминает об этом в POODR. Насколько я помню, она также пропагандирует обертывание переменных только одного экземпляра в методах, даже если они используются только внутри страны. Это помогает избежать сумасшедшего рефакторинга позже. –

Смежные вопросы