В Ruby нет «метода класса». Существуют только методы экземпляра. «Методы класса» на самом деле являются одноточечными методами (на объекте, который как раз так происходит, является экземпляром Class
), а «одиночные методы» - это просто методы экземпляра одноэлементного класса .
private
методы могут быть вызваны только с неявным приемником (который равен self
), т.е. они могут быть вызваны другими методами на одном и том же объекте (что, в свою очередь, означает, что они должны быть методами одного и того же класса или одного из его предков, то есть суперклассов, prepend
ed модулей или include
d модулей).
Это означает, что «методы класса» private
можно назвать только другими «методами класса», то есть методы, определенные в Example
«s одноэлементный класс, Class
, Module
, Object
, Kernel
или BasicObject
. Вы не можете вызвать их из методов, определенных в Example
.
Подумайте об этом: какова цель private
? Его цель - инкапсуляция, мы хотим инкапсулировать внутренним сведениями об осуществлении и представлении от внешних протоколов. В настоящее время широко используются два типа инкапсуляции: абстрактная интуитивно-ориентированная инкапсуляция данных (где экземпляры разных типов инкапсулированы друг в друга, но экземпляры одного и того же типа могут обращаться к внутренним элементам друг друга, например class
es в Java) и Object- Ориентированная инкапсуляция (где разные объекты заключены друг в друга, равно, если они являются экземплярами одного типа, например Ruby и interface
s на Java). Ruby - объектно-ориентированный язык, поэтому он использует объектно-ориентированную инкапсуляцию. (Java OTOH использует ADT-ориентированную инкапсуляцию для классов, что противоречит интуиции, поскольку обычно считается, что это объектно-ориентированный язык).
В вашем случае у нас есть два объекта: Example
и экземпляр Example
. Это два разных объекта, поэтому объектно-ориентированная инкапсуляция просто запрещает вам получать доступ к частным частям одного объекта от другого объекта. Даже если Ruby действительно использовал инкапсуляцию, ориентированную на ADT, она все равно не сработает: инкапсуляция, ориентированная на ADT, позволяет двум экземплярам того же типа обращаться к частным лицам друг друга, но два объекта не одного типа, один экземпляр от Class
, а другой - от Example
.
В принципе, вы хотите управлять внутренними объектами двух объектов одновременно, и это невозможно в ООП. Принципиальный принцип ООП заключается в том, что каждый объект (и каждый объект один) несет ответственность за свои частные части, и вы можете взаимодействовать только с этим объектом, отправляя его сообщения через свой публичный протокол.
tl; dr: то, что вы хотите, прямо противоречит основным принципам инкапсуляции в OO. Либо Example::g
должен быть public
, либо вам нужно изменить свой дизайн. (Прибегая к рефлексивным взломам, чтобы обойти защиту доступа в коде, в котором вы не контролируете, в лучшем случае это запах кода. Прибегать к рефлексивным взломам для защиты доступа в коде, который у вас есть - это просто неправильно, потому что вы контролируете защиту доступа, вы может просто изменить его.)
Одно из возможных решений, чтобы оставить позади ООП в целом, и смотреть на функциональное программирование для помощи. Мы могли бы попытаться использовать затворы для капсулирования:
Мы начинаем с исходного примера:
class Example
private_class_method def self.g
puts 4711
end
def f
self.class.send(:g)
end
end
Example.new.f
# 4711
Теперь мы переходим g
в локальную переменную и присвоить лямбда к нему, и в свою очередь используют что лямбда определить f
:
class Example
g = -> {
puts 4711
}
define_method(:f, &g)
end
Example.new.f
# 4711
Теперь g
есть (в некотором смысле) еще более "частный", чем раньше, becau se он существует только в лексической области тела класса, и даже методы класса, определенные в другом классе, не могут получить к нему доступ. Однако привязка лямбда к g
является подходящим объектом и может быть передана даже в разные области.
Но, по-видимому вы не хотите f
и g
быть просто идентичны (в противном случае вы могли бы просто использовали module_function
, в конце концов), вместо этого вы хотите f
сделать что-то другое, чем просто делегировать g
. Это также возможно:
class Example
g = -> {
puts 4711
}
define_method(:f) do
puts 42
g.()
end
end
Example.new.f
# 42
# 4711
Это работает, потому что в каком-то другом смысле g
является менее «частный», чем раньше: лексические пулы, гнездо, в частности лексические области видимости блоков (и только блоков), так что вложенный блок (в этом случае блок, прошедший в этом случае define_method
) может получить доступ к лексической среде внешней лексической области (в этом случае тело класса) даже после, что лексическая область больше не существует (класс тело закончило оценку).
AFAIK, нет другого способа, кроме как '# send'. – Amadan
Частные методы, как предполагается, вызываются методами внутри. Мы не должны вызывать их непосредственно из экземпляра, поэтому 'Example.new.f' invokes 'g' - правильный способ сделать это. –