Как создать метод частного класса?

216

Как работает этот метод создания метода частного класса:

class Person

  def self.get_name
    persons_name
  end

  class << self

    private

    def persons_name
      "Sam"
    end
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name  #=> raises "private method `persons_name' called for Person:Class (NoMethodError)"

Но это не так:

class Person

  def self.get_name
    persons_name
  end

  private

  def self.persons_name
    "Sam"
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
99miles
источник
7
Я только что видел эту статью, в которой обсуждались способы создания методов частного класса, и подумал, что это хорошо: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/…
Натан Лонг,

Ответы:

265

privateпохоже, не работает, если вы определяете метод для явного объекта (в вашем случае self). Вы можете использовать private_class_methodдля определения методов класса как частные (или как вы описали).

class Person
  def self.get_name
    persons_name
  end

  def self.persons_name
    "Sam"
  end

  private_class_method :persons_name
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name

В качестве альтернативы (в ruby ​​2.1+), поскольку определение метода возвращает символ имени метода, вы также можете использовать это следующим образом:

class Person
  def self.get_name
    persons_name
  end

  private_class_method def self.persons_name
    "Sam"
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
tjwallace
источник
105

ExiRe написал:

Такое поведение рубина действительно расстраивает. Я имею в виду, что если вы перейдете в приватный раздел self.method, то он НЕ будет приватным. Но если вы переместите его в класс << self, то это вдруг сработает. Это просто отвратительно.

Может быть, это сбивает с толку, может быть и разочаровывает, но определенно не противно.

Это имеет смысл, если вы понимаете объектную модель Ruby и соответствующий поток поиска метода , особенно если принять во внимание, что privateэто НЕ модификатор доступа / видимости, а фактически вызов метода (с классом в качестве получателя), как обсуждалось здесь ... в Ruby нет такой вещи, как "приватная секция" .

Чтобы определить частные методы экземпляра , вы вызываете privateкласс экземпляра, чтобы установить видимость по умолчанию для впоследствии определенных методов как частную ... и, следовательно, имеет смысл определять методы частного класса , вызывая privateкласс класса, т.е. его метакласс.

Другие основные, самопровозглашенные ОО-языки могут дать вам менее запутанный синтаксис, но вы определенно поменяете его на запутанную и менее непротиворечивую (несовместимую?) Объектную модель без возможности средств метапрограммирования Ruby.

pvandenberk
источник
Так что, если я правильно понимаю, сам ruby ​​не имеет ключевых слов модификатора доступа (public, private и protected), а имеет методы модификатора доступа (public, private, protected)? Это то, что должно быть включено в систему отслеживания ошибок ruby ​​для Matz, чтобы реализовать правильные модификаторы доступа к ключевым словам, или это ожидаемое поведение?
Эдвард
13
@Edward Это разработано таким образом junichiito.blogspot.co.uk/2012/03/… . Почему "правильно"?
13
1
Исходя из этого, вместо того, чтобы делать, private_class_method :method_nameвы могли бы сделать private_class_method def method_name....
bjt38
send(private_method)также доступен за пределами объекта.
Stevenspiel
1
@ bjt38 Просто для ясности, это будетprivate_class_method def self.method_name
Том
78

По умолчанию все методы класса являются публичными. Чтобы сделать их приватными, вы можете использовать Module # private_class_method, как написало @tjwallace, или определить их по-другому, как вы это сделали:

class << self

  private

  def method_name
    ...
  end
end

class << selfоткрывает одноэлементный класс self, так что методы могут быть переопределены для текущего объекта self. Это используется для определения метода класса / модуля («статического»). Только там, определение частных методов действительно дает вам методы частного класса.

roxxypoxxy
источник
17

Для полноты картины мы также можем избежать объявления private_class_method отдельной строкой. Лично мне не нравится это использование, но приятно знать, что оно существует.

private_class_method  def self.method_name
 ....
end
Эмре Басала
источник
5

Я также считаю, что Ruby (или, по крайней мере, мои знания об этом) не достигли цели в этой области. Например, следующее делает то, что я хочу, но неуклюже,

class Frob
    attr_reader :val1, :val2

    Tolerance = 2 * Float::EPSILON

    def initialize(val1, val2)
        @val2 = val1
        @val2 = val2
        ...
    end

    # Stuff that's likely to change and I don't want part
    # of a public API.  Furthermore, the method is operating
    # solely upon 'reference' and 'under_test' and will be flagged as having
    # low cohesion by quality metrics unless made a class method.
    def self.compare(reference, under_test)
        # special floating point comparison
        (reference - under_test).abs <= Tolerance
    end
    private_class_method :compare

    def ==(arg)
        self.class.send(:compare, val1, arg.val1) &&
        self.class.send(:compare, val2, arg.val2) &&
        ...
    end
end

Мои проблемы с приведенным выше кодом состоят в том, что требования к синтаксису Ruby и мои показатели качества кода соответствуют принятым для громоздкого кода. Чтобы код работал так, как я хочу, и для того, чтобы успокоить метрики, я должен сделать сравнение () методом класса. Поскольку я не хочу, чтобы он был частью класса public API, мне нужно, чтобы он был закрытым, но сам по себе private не работает. Вместо этого я вынужден использовать «private_class_method» или какой-то такой обходной путь. Это, в свою очередь, заставляет использовать «self.class.send (: compare ...» для каждой переменной, которую я проверяю в «== ()». Теперь это немного громоздко.

dinman2022
источник
Тот факт, что вам нужно использовать send , не имеет ничего общего с тем, «как» вы помечаете методы класса как private. Частные методы нельзя вызывать извне.
Паскаль
4

Методы экземпляра определяются внутри блока определения класса. Методы класса определяются как одноэлементные методы для одноэлементного класса класса, также неофициально известного как «метакласс» или «собственный класс». privateэто не ключевое слово, а метод ( Module # private ).

Это вызов метода self#private/, A#privateкоторый «включает» частный доступ для всех последующих определений методов экземпляра, пока не переключится иначе:

class A
  private
    def instance_method_1; end
    def instance_method_2; end
    # .. and so forth
end

Как отмечалось ранее, методы класса - это действительно одноэлементные методы, определенные в одноэлементном классе.

def A.class_method; end

Или используя специальный синтаксис, чтобы открыть тело определения анонимного одноэлементного класса A:

class << A
  def class_method; end
end

Получатель «сообщения приватного» - self- inside class A- это объект класса A. self внутри class << Aблока - это другой объект, синглтон-класс.

В следующем примере в действительности вызывается два разных метода, называемых private , с использованием двух разных получателей или целей для вызова. В первой части мы определяем метод частного экземпляра («в классе A»), во второй мы определяем метод частного класса (фактически это одноэлементный метод в объекте класса singleton класса A).

class A
  # self is A and private call "A.private()"
  private def instance_method; end

  class << self
    # self is A's singleton class and private call "A.singleton_class.private()"
    private def class_method; end
  end
end

Теперь немного перепишем этот пример:

class A
  private
    def self.class_method; end
end

Вы видите ошибку [которую сделали дизайнеры языка Ruby]? Вы переключаетесь на закрытый доступ для всех будущих методов экземпляра A, но приступаете к объявлению одноэлементного метода в другом классе, одноэлементном классе.

Мартин Андерссон
источник
-1

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

#include <iostream>

class C
{
    public:
        void instance_method(void)
        {
            std::cout << "instance method\n";
            class_method();  // !!! LOOK !!! no 'send' required. We can access it
                             // because 'private' allows access within the class
        }
    private:
        void static class_method(void) { std::cout << "class method\n"; }
};

int main()
{
    C c;

    c.instance_method(); // works
    // C::class_method() does not compile - it's properly private
    return 0;
}

Запуск выше

   % ./a.out
   instance method
   class method

Теперь, похоже, Ruby не предоставляет эквивалент. Правила Руби, я думаю, таковы, что частные методы не должны быть доступны с получателем. То есть,

inst.pvt_method  # FAILS
pvt_method # WORKS only within the class (good)

Это нормально для методов частного экземпляра, но вызывает проблемы с методами частного класса.

Я бы хотел, чтобы Ruby функционировал следующим образом:

class C
    def instance_method
        STDOUT << "instance method\n"

        # Simple access to the private class method would be nice:
        class_method   # DOES NOT WORK. RUBY WON'T FIND THE METHOD
        C.class_method # DOES NOT WORK. RUBY WON'T ALLOW IT

        # ONLY THIS WORKS. While I am happy such capability exists I think
        # the way 'send' should be used is when the coder knows he/she is
        # doing a no-no.  The semantic load on the coder for this is also
        # remarkably clumsy for an elegant language like ruby.
        self.class.send(:class_method)
    end

    private_class_method def self.class_method() STDOUT << "class method\n"; end
end

Но, увы, вышесказанное не работает. Кто-нибудь знает лучший способ?

Когда я вижу «send» перед методом, это явный признак того, что код нарушает намерения разработчика API, но в этом случае дизайн специально должен иметь метод экземпляра класса, вызывающий метод private класса.

Дейв Инман
источник
-14

На рубине 2.3.0

class Check
  def self.first_method
    second_method
  end

  private
  def self.second_method
    puts "well I executed"
  end
end

Check.first_method
#=> well I executed
Вамси Паван Махеш
источник
Я пытался сделать это private def self.second_methodперед каждой записью метода, которая не работала на моем Ruby 2.3.3. Но эта запись работает для меня.
Эмиль Врейдагс
11
Это неверно, потому что вызов Check.second_methodтакже будет работать без проблем, поэтому он не является частным.
Дейвин
1
Это не сработает, попробуйте этоprivate_class_method :second_method
KING SABRI