Comment créer une méthode de classe privée?

216

Comment fonctionne cette approche de création d'une méthode de classe privée:

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

Mais cela ne signifie pas:

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 milles
la source
7
Je viens de voir cet article discutant des moyens de créer des méthodes de classe privées et j'ai
Nathan Long

Réponses:

265

privatene semble pas fonctionner si vous définissez une méthode sur un objet explicite (dans votre cas self). Vous pouvez utiliser private_class_methodpour définir des méthodes de classe comme privées (ou comme vous l'avez décrit).

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

Alternativement (dans ruby ​​2.1+), étant donné qu'une définition de méthode renvoie un symbole du nom de la méthode, vous pouvez également l'utiliser comme suit:

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
la source
105

ExiRe a écrit:

Un tel comportement de rubis est vraiment frustrant. Je veux dire si vous passez à la section privée self.method, alors ce n'est PAS privé. Mais si vous le déplacez vers la classe << self, cela fonctionne soudainement. C'est juste dégoûtant.

Le confondre est probablement, frustrant peut-être, mais dégoûtant ce n'est certainement pas.

Cela prend tout son sens une fois que vous comprenez le modèle d'objet de Ruby et le flux de recherche de méthode correspondant , en particulier lorsque vous prenez en considération ce privaten'est PAS un modificateur d'accès / visibilité, mais en fait un appel de méthode (avec la classe comme destinataire) comme discuté ici ... il n'y a pas de "section privée" dans Ruby.

Pour définir des méthodes d' instance privées , vous appelez privatela classe de l'instance pour définir la visibilité par défaut des méthodes définies par la suite sur private ... et il est donc parfaitement logique de définir des méthodes de classe privée en faisant appel privateà la classe de la classe, c'est-à-dire. sa métaclasse.

D'autres langages OO autoproclamés courants peuvent vous donner une syntaxe moins déroutante, mais vous échangez certainement cela contre un modèle d'objet déroutant et moins cohérent (incohérent?) Sans la puissance des installations de métaprogrammation de Ruby.

pvandenberk
la source
Donc, si je comprends bien, ruby ​​lui-même n'a pas de mots-clés de modificateur d'accès (public, privé et protégé) mais a plutôt des méthodes de modificateur d'accès (public, privé, protégé)? Est-ce quelque chose qui devrait être évoqué sur l'outil de suivi des bogues ruby ​​pour que Matz implémente les modificateurs d'accès aux mots clés appropriés ou est-ce un comportement attendu?
Edward
13
@Edward Il est conçu de cette façon junichiito.blogspot.co.uk/2012/03/… . Pourquoi "convenable"?
iain
1
Suite à cela, au lieu de le faire, private_class_method :method_namevous pourriez le faire private_class_method def method_name....
bjt38
send(private_method)est également accessible en dehors de l'objet.
stevenspiel
1
@ bjt38 Juste pour plus de clarté, ce seraitprivate_class_method def self.method_name
Tom
78

Par défaut, toutes les méthodes de classe sont publiques. Pour les rendre privés, vous pouvez utiliser le module # private_class_method comme @tjwallace l'a écrit ou les définir différemment, comme vous l'avez fait:

class << self

  private

  def method_name
    ...
  end
end

class << selfouvre la classe singleton de self, de sorte que les méthodes peuvent être redéfinies pour l'objet self actuel. Ceci est utilisé pour définir la méthode classe / module ("statique"). Seulement là, définir des méthodes privées vous donne vraiment des méthodes de classe privées.

roxxypoxxy
la source
17

Juste pour être complet, nous pouvons également éviter de déclarer private_class_method dans une ligne distincte. Personnellement, je n'aime pas cet usage mais bon de savoir qu'il existe.

private_class_method  def self.method_name
 ....
end
Emre Basala
la source
5

Moi aussi, je trouve Ruby (ou du moins ma connaissance de celui-ci) loin de la marque dans ce domaine. Par exemple, ce qui suit fait ce que je veux mais est maladroit,

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

Mes problèmes avec le code ci-dessus est que les exigences de syntaxe Ruby et mes métriques de qualité de code conspirent pour faire du code encombrant. Pour que le code fonctionne à la fois comme je le souhaite et pour calmer les métriques, je dois faire de compare () une méthode de classe. Comme je ne veux pas qu'elle fasse partie de la classe 'API publique, j'ai besoin qu'elle soit privée, mais' privée 'en elle-même ne fonctionne pas. Au lieu de cela, je suis obligé d'utiliser 'private_class_method' ou une telle solution de contournement. Ceci, à son tour, force l'utilisation de 'self.class.send (: compare ...' pour chaque variable que je teste dans '== ()'. Maintenant c'est un peu compliqué.

dinman2022
la source
Le fait que vous devez utiliser send n'a rien à voir avec le "comment" vous marquez les méthodes de classe comme privées. Les méthodes privées ne peuvent pas être appelées de "l'extérieur".
Pascal
4

Les méthodes d'instance sont définies dans un bloc de définition de classe. Les méthodes de classe sont définies comme des méthodes singleton sur la classe singleton d'une classe, également connues de manière informelle sous le nom de "métaclasse" ou "classe propre". privaten'est pas un mot-clé, mais une méthode ( Module # private ).

Ceci est un appel à method self#private/ A#privatequi "bascule" l'accès privé pour toutes les définitions de méthode d'instance à venir jusqu'à ce qu'il soit basculé autrement:

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

Comme indiqué précédemment, les méthodes de classe sont vraiment des méthodes singleton définies sur la classe singleton.

def A.class_method; end

Ou en utilisant une syntaxe spéciale pour ouvrir le corps de définition de la classe singleton anonyme de A:

class << A
  def class_method; end
end

Le récepteur du "message privé" - self -inside class Aest l'objet de classe A. self inside the class << Ablock est un autre objet, la classe singleton.

L'exemple suivant appelle en réalité deux méthodes différentes appelées privées , en utilisant deux destinataires ou cibles différents pour l'appel. Dans la première partie, nous définissons une méthode d'instance privée ("sur la classe A"), dans la seconde nous définissons une méthode de classe privée (est en fait une méthode singleton sur l'objet classe singleton de 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

Maintenant, réécrivez un peu cet exemple:

class A
  private
    def self.class_method; end
end

Pouvez-vous voir l'erreur [que les concepteurs du langage Ruby] ont commise? Vous basculez sur l'accès privé pour toutes les méthodes d'instance à venir de A, mais continuez à déclarer une méthode singleton sur une classe différente, la classe singleton.

Martin Andersson
la source
-1

Ruby semble fournir une mauvaise solution. Pour expliquer, commencez par un simple exemple C ++ qui montre l'accès aux méthodes de classe privée:

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

Exécution de ce qui précède

   % ./a.out
   instance method
   class method

Maintenant, Ruby ne semble pas fournir l'équivalent. Je pense que les règles de Ruby sont que les méthodes privées ne doivent pas être accessibles avec un récepteur. C'est,

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

C'est OK pour les méthodes d'instances privées, mais cela pose des problèmes avec les méthodes de classes privées.

J'aimerais que Ruby fonctionne de cette façon:

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

Mais, hélas, ce qui précède ne fonctionne pas. Quelqu'un connaît-il une meilleure façon?

Lorsque je vois `` envoyer '' avant une méthode, c'est un signe clair que le code viole l'intention du concepteur de l'API, mais dans ce cas, la conception consiste spécifiquement à avoir une méthode d'instance de la classe appelée la méthode de classe privée.

Dave Inman
la source
-14

À partir de 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
la source
J'essayais cela avec private def self.second_methodavant chaque notation de méthode, qui ne fonctionnait pas sur mon ruby ​​2.3.3. Mais cette notation fonctionne pour moi.
Emile Vrijdags
11
C'est incorrect, car appeler Check.second_methodfonctionnerait également sans problème, donc ce n'est pas vraiment privé.
Deiwin
1
Cela ne fonctionnera pas essayez ceciprivate_class_method :second_method
KING SABRI