Private Modulmethoden in Ruby

108

Ich habe eine zweiteilige Frage

Beste Übung

  • Ich habe einen Algorithmus, der eine Operation an einer Datenstruktur unter Verwendung der öffentlichen Schnittstelle ausführt
  • Derzeit handelt es sich um ein Modul mit zahlreichen statischen Methoden, die bis auf die eine öffentliche Schnittstellenmethode alle privat sind.
  • Es gibt eine Instanzvariable, die von allen Methoden gemeinsam genutzt werden muss.

Dies sind die Optionen, die ich sehen kann, welche sind die besten?:

  • Modul mit statischen Methoden ('Modul' in Ruby)
  • Klasse mit statischen Methoden
  • Mixin- Modul zur Aufnahme in die Datenstruktur
  • Refaktorieren Sie den Teil des Algorithmus, der diese Datenstruktur ändert (sehr klein), und machen Sie daraus ein Mixin, das die statischen Methoden des Algorithmusmoduls aufruft

Technischer Bereich

Gibt es eine Möglichkeit, eine private Modulmethode zu erstellen ?

module Thing
  def self.pub; puts "Public method"; end
  private
  def self.priv; puts "Private method"; end
end

Das privatedrin scheint keine Wirkung zu haben , ich kann trotzdem Thing.privohne Probleme anrufen .

Daniel Beardsley
quelle
5
Zu Ihrer Information, es gibt keine 'statische' Methode in Ruby, sie werden Klasseninstanzmethoden genannt
Brad
31
Ein alter Kommentar, aber da er vier positive Stimmen hat, muss ich darauf hinweisen, dass es keine 'Klasseninstanzmethode' gibt. 'Klassenmethode' ist der richtige Begriff.
Micapam
5
privateBetrifft nur Instanzmethoden, keine Klassenmethoden. Verwenden Sie private_class_methodstattdessen:module Thing; def self.pub; end; private_class_method :pub; end
Apeiros
1
@micapam Klasseninstanzmethoden sind in Ruby vorhanden und unterscheiden sich von Klassenmethoden.
Marnen Laibow-Koser

Antworten:

88

Ich denke, der beste Weg (und vor allem, wie vorhandene Bibliotheken geschrieben werden), dies zu tun, besteht darin, eine Klasse innerhalb des Moduls zu erstellen, die sich mit der gesamten Logik befasst, und das Modul bietet nur eine bequeme Methode, z

module GTranslate
  class Translator
    def perform( text ); translate( text ); end

    private

    def translate( text )
      # do some private stuff here
    end
  end

  def self.translate( text )
    t = Translator.new
    t.perform( text )
  end
end
ucron
quelle
14
Ruby Neuling hier. Wird in diesem Beispiel die Übersetzerklasse als Teil der öffentlichen Schnittstelle des Moduls verfügbar gemacht? Kann der Zugriff auf die 'perform'-Methode auf GTranslate beschränkt sein?
Rshepherd
2
@rshepherd Dies performist nicht die Methode, die hier privat sein soll, die private Methode ist die private Methode in der TranslatorKlasse (@ ucrons Beispiel hat keine, was sehr unglücklich ist). GTranslate.translateist nur eine bequeme Methode für GTranslate::Translator#perform, es gibt keinen wirklichen Gewinn, der es verbirgt, wenn es überhaupt möglich wäre.
Michelpm
28
Ich bin mir nicht sicher, was durch eine Klasse hier erreicht wird. Wenn das Ziel eine private Modulmethode ist, entspricht dies nicht dem Ziel. Weil Sie von außerhalb des Moduls auf die Methode "perform" zugreifen können, indem Sie GTranslate :: Translator.new.perform aufrufen. Mit anderen Worten, es ist nicht privat.
Zack Xu
1
@jschorr Ich denke, die Op und diese Antwort beabsichtigen, eine private Klassen- oder Modulmethode zu erstellen, keine Instanzmethode. Außerdem wird dadurch keine Instanzmethode privat, da self.translateeine Klassen- / Modulmethode deklariert wird.
konsolebox
5
GTranslate::Translator.new.perform(text)- verschlungen, aber nicht privat!
Abhillman
80

Es gibt auch Module.private_class_method, was wohl mehr Absicht ausdrückt.

module Foo
  def self.included(base)
    base.instance_eval do
      def method_name
        # ...
      end
      private_class_method :method_name
    end
  end
end

Für den Code in der Frage:

module Thing
  def self.pub; puts "Public method"; end
  def self.priv; puts "Private method"; end
  private_class_method :priv
end

Ruby 2.1 oder neuer:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
konsolebox
quelle
Ich war mir dessen nicht bewusst. Funktioniert es auch vor der Methodendefinition private?
Marnen Laibow-Koser
7
Diese Antwort zusammen mit der Antwort von @ JCooper ist die echte Lösung. @ MarnenLaibow-Koser Das tut es nicht. Sie können die andere Antwort auf Kosten weiterer Gruppierungen und Einrückungen in Betracht ziehen. Es kann tatsächlich die bevorzugte Lösung für einige sein. (Antwort nur als Referenz.)
konsolebox
58
module Writer
  class << self
    def output(s)
      puts upcase(s)
    end

    private

    def upcase(s)
      s.upcase
    end
  end
end

Writer.output "Hello World"
# -> HELLO WORLD

Writer.upcase "Hello World"
# -> so.rb:16:in `<main>': private method `upcase' called for Writer:Module (NoMethodError)
cdrev
quelle
4
Dies sollte die akzeptierte Antwort sein. Sauber und idiomatisch meiner Meinung nach.
Martin Nyaga
@ MartinNyaga das hat den Nachteil, keine include WriterOption zu haben!
Ulysse BN
28

Sie können die "eingeschlossene" Methode verwenden, um ausgefallene Dinge zu tun, wenn ein Modul eingemischt wird. Dies bewirkt, was Sie wollen, denke ich:

module Foo
  def self.included(base)
    class << base 
      def public_method
        puts "public method"
      end
      def call_private
        private_method
      end
      private
      def private_method
        puts "private"
      end
    end
  end
end

class Bar
  include Foo
end

Bar.public_method

begin
  Bar.private_method
rescue
  puts "couldn't call private method"
end

Bar.call_private
Cameron Price
quelle
5
Das ist schlau. Es ist also möglich, aber wahrscheinlich nicht wert.
Daniel Beardsley
funktioniert gut. Ich habe included do |base| [...] endanstelle von def
Crystark
5
@Crystark: Diese Syntax existiert nur für Module, die ActiveSupport :: Concern erweitern, wenn ich mich nicht irre. dh es ist eine Schienensache.
Vaz
11

privateGilt leider nur für Instanzmethoden. Der allgemeine Weg, um private "statische" Methoden in einer Klasse zu erhalten, besteht darin, Folgendes zu tun:

class << self
  private

  def foo()
   ....
  end
end

Zugegeben, ich habe damit nicht in Modulen gespielt.

J Cooper
quelle
7
Das ist nicht wahr. Sie können private Klassenmethoden und private Modulmethoden verwenden.
Mikeycgto
Sie können private Klassenmethoden haben, aber wenn Sie dies tun, wird keine .fooprivate Klassenmethode erstellt: "private; def self.foo ()"
Ari
@mikeycgto Möchten Sie den Unterschied zwischen Methoden privater Klassen und Methoden privater Module herausarbeiten? Weil ich denke, dass sie genauso sind. Beachten Sie, dass beide privateund private_class_methodsind im Besitz Modulenicht Class. Dieser Code funktioniert übrigens und ist die Alternative zur Verwendung private_class_method.
konsolebox
3

Ein schöner Weg ist so

module MyModule
  class << self
    def public_method
      # you may call the private method here
      tmp = private_method
      :public
    end

    private def private_method
      :private
    end
  end
end

# calling from outside the module
puts MyModule::public_method
Tallak Tveide
quelle
1

Was ist mit dem Speichern von Methoden als Lambdas in Klassenvariablen / Konstanten?

module MyModule
  @@my_secret_method = lambda {
    # ...
  }
  # ...
end

Zum Test:
UPD: Eine große Aktualisierung dieses Codes nach 6 Jahren zeigt eine sauberere Möglichkeit, eine private Methode zu deklarierend

module A
  @@L = lambda{ "@@L" }
  def self.a ; @@L[] ; end
  def self.b ; a ; end

  class << self
    def c ; @@L[] ; end
    private
    def d ; @@L[] ; end
  end
  def self.e ; c ; end
  def self.f ; self.c ; end
  def self.g ; d ; end
  def self.h ; self.d ; end

  private
  def self.i ; @@L[] ; end
  class << self
    def j ; @@L[] ; end
  end

  public
  def self.k ; i ; end
  def self.l ; self.i ; end
  def self.m ; j ; end
  def self.n ; self.j ; end
end

for expr in %w{ A.a A.b A.c A.d A.e A.f A.g A.h A.i A.j A.k A.l A.m A.n }
  puts "#{expr} => #{begin ; eval expr ; rescue => e ; e ; end}"
end

Hier sehen wir das:

A.a => @@L
A.b => @@L
A.c => @@L
A.d => private method `d' called for A:Module
A.e => @@L
A.f => @@L
A.g => @@L
A.h => private method `d' called for A:Module
A.i => @@L
A.j => @@L
A.k => @@L
A.l => @@L
A.m => @@L
A.n => @@L

1) @@Lkann nicht von außen aufgerufen werden, ist aber von fast überall zugänglich
2) class << self ; private ; defmacht die Methode dvon außen und von innen erfolgreich unzugänglich mit - self.aber nicht ohne - das ist seltsam
3) private ; self.und private ; class << selfmacht Methoden nicht privat - sie sind beide zugänglich mit und ohneself.

Nakilon
quelle
Lambdas sind überhaupt nicht dasselbe wie Methoden. Lambdas sind vom Typ Proc, während Methoden vom Typ sind Method.
Michael Dorst
1
globale Variablen sind schlecht
Achempion
@achempion, wo siehst du sie?
Nakilon
@ Nakilon Ich entschuldige mich, bearbeite deine Antwort, wenn du willst, dass ich meine Stimme storniere
achempion
0

Erstellen Sie ein privates Modul oder eine Klasse

Konstanten sind niemals privat. Es ist jedoch möglich, ein Modul oder eine Klasse zu erstellen, ohne sie einer Konstanten zuzuweisen.

Eine Alternative dazu :private_class_methodbesteht darin, ein privates Modul oder eine Klasse zu erstellen und öffentliche Methoden darauf zu definieren.

module PublicModule
  def self.do_stuff(input)
    @private_implementation.do_stuff(input)
  end

  @private_implementation = Module.new do
    def self.do_stuff(input)
      input.upcase # or call other methods on module
    end
  end
end

Verwendung:

PublicModule.do_stuff("whatever") # => "WHATEVER"

Siehe die Dokumente für Module.new und Class.new .

Nathan Long
quelle
Ich mag diese Methode wirklich. Es scheint jedoch nicht möglich zu sein, die .selfin den Methodendefinitionen zu entfernen , sie in eine andere Klasse aufzunehmen und sie als Instanzmethoden der einschließenden Klasse zu verwenden. Wissen Sie, ob es eine Möglichkeit gibt, es zum Laufen zu bringen?
Shiyason
0

Diese Methode erlaubt keine gemeinsame Nutzung von Daten mit den privaten Methoden, es sei denn, Sie übergeben die Daten explizit an Methodenparameter.

module Thing
  extend self

  def pub
    puts priv(123)
  end

  private
  
  def priv(value)
    puts "Private method with value #{value}"
  end
end

Thing.pub
# "Private method with value 123"

Thing.priv
# NoMethodError (private method `priv' called for Thing:Module)
Gerry Shaw
quelle