Jak stworzyć metodę klasy prywatnej?

216

Jak działa to podejście do tworzenia metody klasy prywatnej:

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)"

Ale to nie:

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
99 mil
źródło
7
Właśnie widziałem ten artykuł omawiający sposoby tworzenia metod klasy prywatnej i pomyślałem, że był dobry: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/…
Nathan Long

Odpowiedzi:

265

privatenie działa, jeśli definiujesz metodę na obiekcie jawnym (w twoim przypadku self). Możesz użyć private_class_methoddo zdefiniowania metod klasowych jako prywatnych (lub tak, jak opisano).

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

Alternatywnie (w Ruby 2.1+), ponieważ definicja metody zwraca symbol nazwy metody, możesz również użyć tego w następujący sposób:

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
źródło
105

ExiRe napisał:

Takie zachowanie ruby ​​jest naprawdę frustrujące. Mam na myśli, że jeśli przejdziesz do sekcji prywatnej self.method, wówczas NIE będzie ona prywatna. Ale jeśli przeniesiesz go do klasy << siebie, wtedy nagle zadziała. To jest po prostu obrzydliwe.

Może to być mylące, może być frustrujące, ale zdecydowanie nie jest obrzydliwe.

Ma to sens, gdy zrozumiesz model obiektowy Ruby i przepływ odnośnych metod , szczególnie biorąc pod uwagę, że NIEprivate jest to modyfikator dostępu / widoczności, ale w rzeczywistości wywołanie metody (z klasą jako odbiorcą), jak omówiono tutaj ... w Ruby nie ma czegoś takiego jak „sekcja prywatna” .

Aby zdefiniować prywatne metody instancji , wywołujesz privateklasę instancji, aby ustawić domyślną widoczność dla później zdefiniowanych metod na prywatne ... i dlatego sensowne jest definiowanie prywatnych metod klasy poprzez wywołanie privateklasy klasy, tj. jego metaklasa.

Inne główne, samozwańcze języki OO mogą dawać mniej mylącą składnię, ale zdecydowanie wymieniasz to na mylący i mniej spójny (niespójny?) Model obiektowy bez możliwości narzędzi do metaprogramowania Ruby.

pvandenberk
źródło
Więc jeśli dobrze rozumiem, sam Ruby nie ma słów kluczowych modyfikatora dostępu (publicznego, prywatnego i chronionego), ale raczej metody modyfikatora dostępu (publicznego, prywatnego, chronionego)? Czy to coś, co należy poruszyć w narzędziu do śledzenia błędów ruby, aby Matz wdrożył odpowiednie modyfikatory dostępu do słów kluczowych, czy jest to oczekiwane zachowanie?
Edward
13
@ Edward Zaprojektowano w ten sposób junichiito.blogspot.co.uk/2012/03/… . Dlaczego „właściwe”?
iain
1
Po tym, zamiast robić, private_class_method :method_namemożesz to zrobić private_class_method def method_name....
bjt38,
send(private_method)jest również dostępny poza obiektem.
stevenspiel
1
@ bjt38 Dla jasności byłoby toprivate_class_method def self.method_name
Tom
78

Domyślnie wszystkie metody klas są publiczne. Aby ustawić je jako prywatne, możesz użyć Module # private_class_method jak napisał @tjwallace lub zdefiniować je inaczej, tak jak zrobiłeś:

class << self

  private

  def method_name
    ...
  end
end

class << selfotwiera singletonową klasę self, dzięki czemu można przedefiniować metody dla bieżącego obiektu self. Służy do definiowania metody klasa / moduł („statyczna”). Tylko tam zdefiniowanie metod prywatnych naprawdę daje metody klasy prywatnej.

roxxypoxxy
źródło
17

Dla kompletności możemy również uniknąć zadeklarowania metody private_class_metod w osobnym wierszu. Osobiście nie lubię tego użycia, ale dobrze wiedzieć, że istnieje.

private_class_method  def self.method_name
 ....
end
Emre Basala
źródło
5

Ja też uważam, że Ruby (a przynajmniej moja wiedza na ten temat) nie ma znaku w tym obszarze. Na przykład poniższe czynności robią to, co chcę, ale są niezdarne,

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

Moje problemy z powyższym kodem polegają na tym, że wymagania dotyczące składni Ruby i metryki jakości kodu są zgodne z tym, co jest kłopotliwe. Aby kod działał tak, jak chcę, i wyciszać metryki, muszę zrobić z metody replace () metodę klasy. Ponieważ nie chcę, aby był on częścią publicznego API klasy, muszę być prywatny, ale sam „prywatny” sam w sobie nie działa. Zamiast tego jestem zmuszony użyć metody „private_class_method” lub innej takiej metody. To z kolei wymusza użycie „self.class.send (: porównaj ...” dla każdej zmiennej, którą testuję w „== ()”. Teraz jest to trochę niewygodne.

dinman2022
źródło
Fakt, że musisz użyć send , nie ma nic wspólnego z tym, w jaki sposób oznaczasz metody klas jako prywatne. Metod prywatnych nie można wywoływać z „z zewnątrz”.
Pascal
4

Metody instancji są zdefiniowane w bloku definicji klasy. Metody klasowe są zdefiniowane jako metody singletonowe w klasie singleton klasy, zwanej także nieformalnie „metaklasą” lub „klasą elektroniczną”. privatenie jest słowem kluczowym, ale metodą ( moduł nr prywatny ).

Jest to wywołanie metody self#private/, A#privatektóre „włącza” prywatny dostęp do wszystkich przyszłych definicji metod instancji, dopóki nie zostanie przełączone inaczej:

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

Jak wspomniano wcześniej, metody klasowe są tak naprawdę metodami singletonowymi zdefiniowanymi w klasie singleton.

def A.class_method; end

Lub używając specjalnej składni, aby otworzyć treść definicji anonimowej, singletonowej klasy A:

class << A
  def class_method; end
end

Odbiorca „wiadomości prywatnej” - self - inside class Ajest obiektem klasy A. self wewnątrz class << Abloku to kolejny obiekt, klasa singleton.

Poniższy przykład w rzeczywistości wywołuje dwie różne metody nazywane prywatnymi , używając dwóch różnych odbiorców lub celów połączenia. W pierwszej części definiujemy metodę instancji prywatnej („w klasie A”), w drugiej definiujemy metodę klasy prywatnej (w rzeczywistości jest to metoda singletonu na obiekcie klasy 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

Teraz przepisz trochę ten przykład:

class A
  private
    def self.class_method; end
end

Czy widzisz błąd [popełniony przez projektantów języka Ruby]? Przełączasz na dostęp prywatny dla wszystkich przyszłych metod instancji klasy A, ale kontynuujesz deklarowanie metody singleton w innej klasie, klasie singleton.

Martin Andersson
źródło
-1

Ruby wydaje się słabym rozwiązaniem. Aby wyjaśnić, zacznij od prostego przykładu C ++, który pokazuje dostęp do metod klasy prywatnej:

#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;
}

Uruchamianie powyższego

   % ./a.out
   instance method
   class method

Teraz Ruby nie wydaje się odpowiadać. Sądzę, że zasady Ruby mówią, że nie można uzyskać dostępu do prywatnych metod za pomocą odbiornika. To jest,

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

To jest OK w przypadku metod instancji prywatnej, ale powoduje problemy z metodami klasy prywatnej.

Chciałbym, aby Ruby działała w ten sposób:

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

Ale niestety powyższe nie działa. Czy ktoś zna lepszy sposób?

Kiedy widzę „wyślij” przed metodą, jest to wyraźny znak, że kod narusza intencję projektanta API, ale w tym przypadku projekt ma konkretnie wywołać metodę instancji klasy wywołującą metodę klasy prywatnej.

Dave Inman
źródło
-14

Od ruby ​​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
Vamsi Pavan Mahesh
źródło
Próbowałem tego z private def self.second_methodkażdą notacją metody, która nie działała na moim ruby ​​2.3.3. Ale ten zapis działa dla mnie.
Emile Vrijdags
11
Jest to niepoprawne, ponieważ dzwonienie Check.second_methodrównież działałoby bez problemu, więc nie jest tak naprawdę prywatne.
Deiwin,
1
To nie zadziała, spróbuj tegoprivate_class_method :second_method
KING SABRI