Различия между Public, Protected и Private методами

Очень просто программировать несколько лет на Ruby и никогда не обращать внимание на декларирование видимости методов...

Posted by Марк Мельник on December 19, 2010

Очень просто программировать несколько лет на Ruby и никогда не обращать внимание на декларирование видимости методов (прав доступа к методам) при помощи public, private и protected. Я так говорю потому, что и сам не обращал на это внимание. Я пришел в Ruby из С/C++, поэтому я отлично понимал концепцию public и private, и я догадывался о том, что собой представляет декларация protected в Ruby, но я никогда не пытался разобраться со всем этим более подробно. В конечном счете по некоторым причинам я заинтересовался этим вопросом и в этой статье я расскажу о различных декларациях видимости методов: public, protected и private.

Примеры

Я не хочу скучно приводить примеры использования. В пояснении разницы между декларациями видимости методов мне помогут самые обыкновенные хоббиты. Они дружественны и открыты для других, но и у них есть также личная жизнь. Многое из их личной жизни доступно для других хоббитов так как в своем хоббитовом царстве они могут бродить по жилищам друг друга. Конечно, каждый хоббит имеет что-то очень личное, такое, что знает один лишь он и никому и никогда не раскроет своей тайны. Такие особенности устройства поведения хоббитов очень похожи на методы в Ruby. Давайте представим каждого хоббита как объект. Кстати, вот и класс для создания хоббитов:

class Hobbit
  def initialize(name, rooms, has_ring)
    @name, @rooms, @has_ring = name, rooms, has_ring
  end

  def name
    @name
  end

  def name_of(hobbit)
    hobbit.name
  end

  def rooms_of(hobbit)
    hobbit.rooms
  end

  def hobbit_has_ring?(hobbit)
    hobbit.has_ring?
  end

  protected

  def rooms
    @rooms
  end

  private

  def has_ring?
    @has_ring
  end
end

Каждый хоббит имеет 3 атрибута. Имя хоббита (name) декларировано как public, число комнат в доме хоббита (rooms_of) декларировано как protected и наличие кольца (hobbit_has_ring?) — private. name, rooms_of и hobbit_has_ring? — это методы аксессоры для получения значений соответствующих атрибутов объекта хоббита. Теперь давайте создадим пару хоббитов:

irb(main):001:0> require ‘hobbit.rb’
=> true
irb(main):002:0> frodo = Hobbit.new(‘Frodo’, 3, true)
=> #

irb(main):003:0> samwise = Hobbit.new(‘Samwise’, 2, false)
=> #

Публичные (public) методы

В Ruby все методы, для которых явно не декларирована видимость являются public, это означает, что метод доступен ото всюду, из любой части кода, которая знает какому объекту принадлежит метод. Мы можем легко узнать имена каждого хоббита:

irb(main):004:0> frodo.name
=> «Frodo»
irb(main):005:0> samwise.name
=> «Samwise»

Защищенные (protected) методы

Что насчет комнат в их домах? Эта информация доступна только через protected-методы:

irb(main):006:0> frodo.rooms
NoMethodError: protected method `rooms’ called for #
from (irb):6
from :0

irb(main):007:0> frodo.rooms_of(frodo)
=> 3
irb(main):008:0> frodo.rooms_of(samwise)
=> 2

Как вы видите, мы не можем вызвать вызвать protected — метод объекта непосредственно, но один объект может вызвать защищенный метод для себя или любого другого родственного объекта. Таким образом хоббиты скрывают количество комнат в своих домах от других живых существ, но между собой у них в этом секрета нет. Если перейти на чисто программерскую терминологию, то public-методы доступны глобально, а protected могут вызываться только через методы родственных объектов (произошедших от того же класса).

Приватные (private) методы

Что насчет наличия кольца у хоббита? Метод доступа к этой информации помечен как private. Как же мы можем получить к ней доступ?

irb(main):013:0> frodo.has_ring?
NoMethodError: private method `has_ring?’ called for #
from (irb):13
from :0

irb(main):014:0> frodo.hobbit_has_ring?(frodo)
NoMethodError: private method `has_ring?’ called for #
from ./hobbit.rb:19:in `hobbit_has_ring?’
from (irb):14
from :0

irb(main):015:0> frodo.hobbit_has_ring?(samwise)
NoMethodError: private method `has_ring?’ called for #
from ./hobbit.rb:19:in `hobbit_has_ring?’
from (irb):15
from :0

Мы не можем получить доступ к методу has_ring? непосредственно, и не можем получить доступ через другие методы объекта, даже если хоббит спрашивает сам себя. Давайте добавим в класс Hobbit новый метод i_have_the_ring?

class Hobbit
  def i_have_the_ring?
    has_ring?
  end
end

Теперь у frodo есть способ проверить есть ли у него кольцо:

irb(main):021:0> frodo.i_have_the_ring?
=> true

В Ruby protected методы остаются открытыми для других объектов того же типа. По этому доступ к protected методу гарантирован пока есть объект — «приемник» (hobbit в hobbit.rooms) произошедший от того же класса что и объект метод которого мы хотим вызвать.

Private — методы позволяют вызывать себя только в контексте собственного объекта. Мы не можем вызвать их как: hobbit.has_ring? или даже так: self.has_ring?, но мы можем воспользоваться private методом просто вызвав его в контексте объекта: has_ring?.

Заключение

public — полностью открытые методы. Различие между protected и private может вызывать проблемы если вы не осведомлены о нем. В Ruby декларация private является даже более строгой, чем private в языке Си. Декларация protected является золотой серединой между public и private позволяя одному хоббиту узнать число комнат в доме другого ;-)

Я использую protected-методы когда они не предназначены для непосредственного вызова и я не пишу для них тестов. Я пишу тесты для public-методов которые вызывают методы обозначенные как protected. Кроме того, я избегаю private-методов потому что я не могу представить потребность блокировать доступ родственных объектов к методам друг-друга (хотя я вероятно должен если я не проверяю их), кроме того из-за использования private методов могут возникать проблемы с дебагом.

Комментарий от переводчика:
Автор поста не призывает вас не использовать private методов, он просто делится своим опытом и утверждает, что просто не видит в ней необходимости а в силу возможных проблем с дебагом старается избегать.

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

Оригинал на английском здесь: Ruby Method Permissions: The Differences Between Public, Protected, and Private

Лучшая благодарность автору блога — ваши комментарии! Больше комментариев — больше отличных постов ;-)