Bagaimana cara membuat metode kelas privat?

216

Bagaimana pendekatan menciptakan metode kelas privat ini bekerja:

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

Tetapi ini tidak:

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
sumber
7
Saya baru saja melihat artikel ini membahas cara membuat metode kelas privat dan berpikir itu bagus: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/...
Nathan Long

Jawaban:

265

privatetampaknya tidak berfungsi jika Anda mendefinisikan metode pada objek eksplisit (dalam kasus Anda self). Anda dapat menggunakan private_class_methoduntuk mendefinisikan metode kelas sebagai pribadi (atau seperti yang Anda jelaskan).

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

Atau (dalam ruby ​​2.1+), karena definisi metode mengembalikan simbol nama metode, Anda juga dapat menggunakan ini sebagai berikut:

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
sumber
105

ExiRe menulis:

Perilaku ruby ​​seperti itu benar-benar membuat frustrasi. Maksud saya jika Anda pindah ke bagian pribadi self.method maka itu TIDAK pribadi. Tetapi jika Anda memindahkannya ke kelas << sendiri maka tiba-tiba berfungsi. Itu hanya menjijikkan.

Membingungkan mungkin, membuat frustrasi mungkin, tetapi menjijikkan itu pasti tidak.

Itu masuk akal setelah Anda memahami model obyek Ruby dan sesuai metode pencarian mengalir , terutama ketika mempertimbangkan bahwa privateadalah TIDAK pengubah akses / visibilitas, namun sebenarnya metode panggilan (dengan kelas sebagai penerima nya) seperti yang dibahas di sini ... tidak ada yang namanya "bagian pribadi" di Ruby.

Untuk mendefinisikan metode instance privat , Anda memanggil privatekelas instance untuk mengatur visibilitas default untuk metode yang didefinisikan selanjutnya menjadi privat ... dan karenanya masuk akal untuk mendefinisikan metode kelas privat dengan memanggil privatekelas class, yaitu. metaclass-nya.

Bahasa OO arus utama yang diproklamirkan sendiri dapat memberikan Anda sintaks yang kurang membingungkan, tetapi Anda pasti menukarnya dengan model objek yang membingungkan dan kurang konsisten (tidak konsisten?) Tanpa kekuatan fasilitas pemrograman metaprogram Ruby.

pvandenberk
sumber
Jadi jika saya memahaminya dengan benar, ruby ​​sendiri tidak memiliki kata kunci pengubah akses (publik, pribadi, dan terlindungi) melainkan memiliki metode pengubah akses (publik, pribadi, terlindungi)? Apakah ini sesuatu yang harus diangkat pada pelacak bug ruby ​​untuk Matz untuk menerapkan pengubah akses kata kunci yang tepat atau apakah ini perilaku yang diharapkan?
Edward
13
@Edward Dirancang dengan cara seperti itu junichiito.blogspot.co.uk/2012/03/… . Kenapa "pantas"?
iain
1
Mengikuti dari ini, bukannya melakukan private_class_method :method_nameyang bisa Anda lakukan private_class_method def method_name....
bjt38
send(private_method)juga dapat diakses di luar objek.
stevenspiel
1
@ bjt38 Hanya untuk kejelasan, itu akanprivate_class_method def self.method_name
Tom
77

Secara default semua metode kelas adalah publik. Untuk menjadikannya pribadi, Anda dapat menggunakan Module # private_class_method seperti @tjwallace menulis atau mendefinisikannya secara berbeda, seperti yang Anda lakukan:

class << self

  private

  def method_name
    ...
  end
end

class << selfmembuka kelas singleton diri, sehingga metode dapat didefinisikan ulang untuk objek diri saat ini. Ini digunakan untuk mendefinisikan metode kelas / modul ("statis"). Hanya di sana, mendefinisikan metode pribadi benar-benar memberi Anda metode kelas pribadi.

roxxypoxxy
sumber
17

Hanya untuk kelengkapannya, kita juga dapat menghindari mendeklarasikan private_class_method dalam baris terpisah. Saya pribadi tidak suka penggunaan ini tetapi senang mengetahui bahwa itu ada.

private_class_method  def self.method_name
 ....
end
Emre Basala
sumber
5

Saya juga menemukan Ruby (atau setidak-tidaknya sepengetahuan saya) singkat dari tanda di area ini. Sebagai contoh, berikut ini melakukan apa yang saya inginkan tetapi canggung,

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

Masalah saya dengan kode di atas adalah bahwa persyaratan sintaksis Ruby dan metrik kualitas kode saya berkonspirasi untuk dibuat untuk kode rumit. Agar kode berfungsi baik seperti yang saya inginkan dan untuk menenangkan metrik, saya harus membandingkan () metode kelas. Karena saya tidak ingin itu menjadi bagian dari API publik kelas, saya ingin itu menjadi pribadi, namun 'pribadi' dengan sendirinya tidak berfungsi. Alih-alih saya terpaksa menggunakan 'private_class_method' atau beberapa solusi semacam itu. Ini, pada gilirannya, memaksa penggunaan 'self.class.send (: bandingkan ...' untuk setiap variabel yang saya uji di '== ()'. Sekarang agak sulit.

dinman2022
sumber
Fakta bahwa Anda perlu menggunakan send tidak ada hubungannya dengan "bagaimana" Anda menandai metode kelas pribadi. Metode pribadi tidak dapat dipanggil dari "luar".
Pascal
4

Metode instance didefinisikan di dalam blok definisi kelas. Metode kelas didefinisikan sebagai metode singleton pada kelas singleton suatu kelas, juga secara informal dikenal sebagai "metaclass" atau "eigenclass". privatebukan kata kunci, tetapi metode ( Modul # pribadi ).

Ini adalah panggilan ke metode self#private/ A#privateyang "mengaktifkan" akses pribadi untuk semua definisi metode instance yang akan datang hingga beralih sebaliknya:

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

Seperti disebutkan sebelumnya, metode kelas benar-benar metode tunggal yang didefinisikan pada kelas tunggal.

def A.class_method; end

Atau menggunakan sintaks khusus untuk membuka tubuh definisi dari kelas A anonim, tunggal:

class << A
  def class_method; end
end

Penerima "pesan pribadi" - diri di dalam class Aadalah objek kelas A. diri di dalam class << Ablok adalah objek lain, kelas singleton.

Contoh berikut ini pada kenyataannya memanggil dua metode berbeda yang disebut pribadi , menggunakan dua penerima atau target yang berbeda untuk panggilan tersebut. Pada bagian pertama, kita mendefinisikan metode instance privat ("pada kelas A"), pada yang kedua kita mendefinisikan metode kelas privat (sebenarnya metode singleton pada objek kelas singleton dari 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

Sekarang, tulis ulang contoh ini sedikit:

class A
  private
    def self.class_method; end
end

Bisakah Anda melihat kesalahan [yang dilakukan desainer bahasa Ruby]? Anda beralih pada akses pribadi untuk semua metode instance A yang akan datang, tetapi lanjutkan untuk mendeklarasikan metode singleton pada kelas yang berbeda, kelas singleton.

Martin Andersson
sumber
-1

Ruby tampaknya memberikan solusi yang buruk. Untuk menjelaskan, mulailah dengan contoh C ++ sederhana yang memperlihatkan akses ke metode kelas privat:

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

Menjalankan di atas

   % ./a.out
   instance method
   class method

Sekarang Ruby tampaknya tidak memberikan yang setara. Menurut saya, aturan Ruby adalah bahwa metode pribadi tidak boleh diakses dengan penerima. Itu adalah,

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

Tidak masalah untuk metode instance pribadi, tetapi menyebabkan masalah dengan metode kelas privat.

Saya ingin Ruby berfungsi dengan cara ini:

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

Tapi, sayangnya, hal di atas tidak berhasil. Apakah ada yang tahu cara yang lebih baik?

Ketika saya melihat 'kirim' sebelum suatu metode, itu adalah tanda yang jelas bahwa kode tersebut melanggar maksud perancang API, tetapi dalam hal ini desainnya secara khusus memiliki metode instance dari panggilan kelas metode kelas privat.

Dave Inman
sumber
-14

Pada 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
sumber
Saya mencoba ini dengan private def self.second_methodsebelum masing-masing metode notasi, yang tidak bekerja pada batu delima saya 2.3.3. Tetapi notasi ini bekerja untuk saya.
Emile Vrijdags
11
Ini tidak benar, karena panggilan Check.second_methodjuga berfungsi tanpa masalah, jadi itu tidak benar-benar pribadi.
Deiwin
1
Tidak akan berhasil coba iniprivate_class_method :second_method
RAJA SABRI