2016-08-09 3 views
0

У меня есть класс, где я хочу, чтобы один метод класса использовался только другим классом и экземпляром методов, но не из других классов - другими словами, я хотел бы иметь a частный класс способ. После небольшого исследования я пришел к следующему подходу:Использование или неправильное использование методов частного класса в Ruby

class Example 
    def initialize 
    end 
    def f 
    # My private class method will be called here 
    self.class.send(:g) 
    end 
    # g is going to be a private class method 
    def self.g 
    puts 4711 
    end 
    private_class_method :g 
end 

Это работает до сих пор; вызов Example.new.f вызывает g и при вызове Example.g выдает исключение, если требуется.

Что неловко, это способ вызова g (self.class.send(:g)). Есть ли лучший способ сделать это? Или это вообще плохая идея сделать метод класса закрытым, когда его нужно вызвать методом экземпляра?

+0

AFAIK, нет другого способа, кроме как '# send'. – Amadan

+0

Частные методы, как предполагается, вызываются методами внутри. Мы не должны вызывать их непосредственно из экземпляра, поэтому 'Example.new.f' invokes 'g' - правильный способ сделать это. –

ответ

3

В 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) может получить доступ к лексической среде внешней лексической области (в этом случае тело класса) даже после, что лексическая область больше не существует (класс тело закончило оценку).

+0

Это абсолютно отличный анализ проблемы !!! Я даже не знал, что локальные переменные внутри определения класса могут быть полезны! – user1934428

1

Вы не можете вызывать методы частного класса непосредственно из его методов экземпляра, потому что Руби private является уровень экземпляра частного, что означает частный метод может быть вызван только на текущем объекте (неявный self). Чтобы вызвать частный метод другого объекта, самым простым способом является использование send.

Поскольку класс и его экземпляры представляют собой совершенно разные объекты, вы должны использовать или другие трюки метапрограммирования.

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