วิธีโมดูลส่วนตัวใน Ruby


108

ฉันมีคำถามสองส่วน

ปฏิบัติที่ดีที่สุด

  • ฉันมีอัลกอริทึมที่ดำเนินการบางอย่างกับโครงสร้างข้อมูลโดยใช้อินเทอร์เฟซสาธารณะ
  • ปัจจุบันเป็นโมดูลที่มีวิธีการแบบคงที่จำนวนมากซึ่งเป็นแบบส่วนตัวทั้งหมดยกเว้นวิธีการเชื่อมต่อสาธารณะเพียงวิธีเดียว
  • มีตัวแปรอินสแตนซ์หนึ่งตัวที่ต้องใช้ร่วมกันระหว่างวิธีการทั้งหมด

นี่คือตัวเลือกที่ฉันเห็นซึ่งดีที่สุด:

  • โมดูลที่มีวิธีการแบบคงที่ ('โมดูล' ในทับทิม)
  • คลาสด้วยวิธีการคงที่
  • โมดูลMixinสำหรับรวมไว้ในโครงสร้างข้อมูล
  • Refactorออกเป็นส่วนหนึ่งของขั้นตอนวิธีที่ปรับเปลี่ยนโครงสร้างข้อมูล (เล็กมาก) และให้ที่ mixin ที่เรียกวิธีการคงที่ของโมดูลอัลกอริทึมที่

ส่วนทางเทคนิค

มีวิธีใดในการสร้างเมธอด Private Moduleหรือไม่?

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

privateในนั้นดูเหมือนจะไม่ได้มีผลกระทบใด ๆก็ยังสามารถเรียกThing.privโดยไม่มีปัญหา


5
FYI ไม่มีเมธอด 'คงที่' ในทับทิมพวกเขาเรียกว่า class instance method
brad

31
ความคิดเห็นเก่า แต่เนื่องจากมีการโหวตถึงสี่ครั้งฉันจึงต้องชี้ให้เห็นว่าไม่มีสิ่งที่เรียกว่า 'class instance method' 'Class method' เป็นคำที่ถูกต้อง
micapam

5
privateมีผลเฉพาะเมธอดของอินสแตนซ์ไม่ใช่วิธีคลาส ใช้private_class_methodแทน:module Thing; def self.pub; end; private_class_method :pub; end
apeiros

1
วิธีการอินสแตนซ์คลาส @micapam มีอยู่ใน Ruby และแตกต่างจากวิธีคลาส
Marnen Laibow-Koser

คำตอบ:


88

ฉันคิดว่าวิธีที่ดีที่สุด (และส่วนใหญ่จะเขียน libs ที่มีอยู่) ในการทำสิ่งนี้คือการสร้างคลาสภายในโมดูลที่เกี่ยวข้องกับตรรกะทั้งหมดและโมดูลก็มีวิธีการที่สะดวกเช่น

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

14
Ruby newb ที่นี่ ในตัวอย่างนี้คลาส Translator ถูกเปิดเผยเป็นส่วนหนึ่งของอินเทอร์เฟซสาธารณะของโมดูลหรือไม่ วิธีการ 'ดำเนินการ' สามารถ จำกัด การเข้าถึงเฉพาะ GTranslate ได้หรือไม่?
rshepherd

2
@rshepherd performไม่ใช่เมธอดที่ควรจะเป็นส่วนตัวที่นี่เมธอดส่วนตัวคือเมธอดส่วนตัวในTranslatorคลาส (ตัวอย่างของ @ ucron ไม่มีเลยเป็นอะไรที่โชคร้ายมาก) GTranslate.translateเป็นเพียงวิธีการที่สะดวกสำหรับGTranslate::Translator#performไม่มีผลประโยชน์ใด ๆ ที่ซ่อนอยู่หากเป็นไปได้
michelpm

28
ฉันไม่แน่ใจว่าจะได้อะไรจากการเรียนที่นี่ หากเป้าหมายคือมีเมธอดโมดูลส่วนตัวก็จะไม่เป็นไปตามเป้าหมาย เนื่องจากคุณสามารถเข้าถึงเมธอด "perform" จากภายนอกโมดูลได้โดยเรียก GTranslate :: Translator.new.perform ในคำอื่น ๆ จะไม่เป็นส่วนตัว
Zack Xu

1
@jschorr ฉันคิดว่า Op และคำตอบนี้ตั้งใจจะสร้างคลาสส่วนตัวหรือวิธีโมดูลไม่ใช่วิธีอินสแตนซ์ นอกจากนี้จะไม่ทำให้วิธีการอินสแตนซ์เป็นส่วนตัวเนื่องจากself.translateประกาศเมธอดคลาส / โมดูล
konsolebox

5
GTranslate::Translator.new.perform(text)- ซับซ้อน แต่ไม่เป็นส่วนตัว!
abhillman

80

นอกจากนี้ยังมีModule.private_class_methodเนื้อหาที่แสดงออกถึงเจตนามากกว่า

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

สำหรับรหัสในคำถาม:

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

Ruby 2.1 หรือใหม่กว่า:

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

ฉันไม่ทราบเรื่องนี้ มันจะทำงานก่อนนิยามวิธีการด้วยเช่นกันprivate?
Marnen Laibow-Koser

7
คำตอบนี้พร้อมกับคำตอบของ @ JCooperคือทางออกที่แท้จริง @ MarnenLaibow-Koser มันไม่ได้ คุณสามารถพิจารณาคำตอบอื่นโดยมีค่าใช้จ่ายในการจัดกลุ่มและการเยื้องที่มากขึ้น อาจเป็นทางออกที่ดีสำหรับบางคน (ตอบไปเพื่อประโยชน์ในการอ้างอิง)
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)

4
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ สะอาดและสำนวนในความคิดของฉัน
Martin Nyaga

@MartinNyaga นี้มีข้อเสียคือไม่มีinclude Writerตัวเลือก!
Ulysse BN

28

คุณสามารถใช้วิธี "รวม" เพื่อทำสิ่งที่แปลกใหม่เมื่อมีการผสมผสานโมดูลเข้าด้วยกันสิ่งนี้จะเกี่ยวกับสิ่งที่คุณต้องการฉันคิดว่า:

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

5
ที่ฉลาด เป็นไปได้ แต่อาจไม่คุ้มค่า
Daniel Beardsley

ใช้งานได้ดี ฉันใช้included do |base| [...] endแทน def
Crystark

5
@Crystark: ไวยากรณ์นั้นมีอยู่เฉพาะในโมดูลที่ขยาย ActiveSupport :: กังวลถ้าฉันไม่เข้าใจผิด คือมันเป็นราง
Vaz

11

น่าเสียดายที่privateใช้ได้กับวิธีการของอินสแตนซ์เท่านั้น วิธีทั่วไปในการรับเมธอด "คงที่" ส่วนตัวในคลาสคือการทำสิ่งต่อไปนี้

class << self
  private

  def foo()
   ....
  end
end

เป็นที่ยอมรับว่าฉันไม่ได้เล่นกับการทำสิ่งนี้ในโมดูล


7
นี่ไม่เป็นความจริง. คุณสามารถมีเมธอดคลาสส่วนตัวและเมธอดโมดูลส่วนตัว
mikeycgto

คุณสามารถมีเมธอดคลาสส่วนตัวได้ แต่การทำเช่นนี้จะไม่ทำให้.fooเมธอดคลาสส่วนตัว: "private; def self.foo ()"
Ari

@mikeycgto ดูแลเพื่ออธิบายความแตกต่างระหว่างเมธอดคลาสส่วนตัวและเมธอดโมดูลส่วนตัวอย่างละเอียด? เพราะฉันคิดว่ามันเหมือนกัน โปรดทราบว่าทั้งสองprivateและprivate_class_methodเป็นเจ้าของโดยไม่ได้Module รหัสนี้จะทำงานโดยวิธีการและมันก็เป็นทางเลือกที่จะใช้Class private_class_method
konsolebox

3

วิธีที่ดีเป็นเช่นนี้

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

1

สิ่งที่เกี่ยวกับการจัดเก็บวิธีการเป็น lambdas ภายในตัวแปรคลาส / ค่าคงที่?

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

สำหรับการทดสอบ:
UPD: การอัปเดตรหัสนี้ครั้งใหญ่หลังจากผ่านไป 6 ปีแสดงให้เห็นวิธีที่สะอาดกว่าในการประกาศวิธีการส่วนตัวd

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

ที่นี่เราเห็นว่า:

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) @@Lไม่สามารถเข้าถึงได้จากภายนอก แต่สามารถเข้าถึงได้จากเกือบทุกที่
2) class << self ; private ; defทำให้วิธีการนี้dไม่สามารถเข้าถึงได้จากภายนอกและภายในด้วยself.แต่ไม่สามารถเข้าถึงได้ - นี่เป็นเรื่องแปลก
3) private ; self.และprivate ; class << selfอย่าทำให้วิธีการเป็นส่วนตัว - สามารถเข้าถึงได้ทั้งสองอย่าง มีและไม่มีself.


lambdas ไม่ได้เหมือนกับวิธีการเลย lambdas เป็นประเภทในขณะที่วิธีการเป็นประเภทProc Method
Michael Dorst

1
ตัวแปรทั่วโลกไม่ดี
achempion

@achempion คุณเห็นพวกเขาที่ไหน?
Nakilon

@ Nakilon ขอโทษของฉันแก้ไขคำตอบของคุณหากคุณต้องการให้ฉันยกเลิกการโหวต
achempion

0

สร้างโมดูลหรือคลาสส่วนตัว

ค่าคงที่ไม่เคยเป็นส่วนตัว อย่างไรก็ตามเป็นไปได้ที่จะสร้างโมดูลหรือคลาสโดยไม่กำหนดให้เป็นค่าคงที่

ดังนั้นอีกทางเลือกหนึ่ง:private_class_methodคือการสร้างโมดูลหรือคลาสส่วนตัวและกำหนดวิธีการสาธารณะในนั้น

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

การใช้งาน:

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

ดูเอกสารสำหรับModule.newและClass.new


ฉันชอบวิธีนี้มาก แต่ดูเหมือนจะเป็นไปไม่ได้ที่จะลบ.selfคำจำกัดความในวิธีการรวมไว้ในคลาสอื่นและใช้เป็นอินสแตนซ์ของคลาสรวม คุณรู้หรือไม่ว่ามีวิธีใดบ้างที่จะทำให้มันใช้งานได้?
Shiyason

0

วิธีนี้จะไม่อนุญาตให้แชร์ข้อมูลด้วยเมธอดส่วนตัวเว้นแต่คุณจะส่งข้อมูลโดยใช้พารามิเตอร์วิธีการอย่างชัดเจน

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.