จะสร้างวิธีเรียนแบบส่วนตัวได้อย่างไร?


216

วิธีการในการสร้างวิธีการเรียนแบบส่วนตัวนี้เป็นอย่างไร

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

แต่นี่ไม่ได้:

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

7
ฉันเพิ่งเห็นบทความนี้พูดถึงวิธีการสร้างวิธีการเรียนส่วนตัวและคิดว่ามันดี: jakeyesbeck.com 2016/01/24/ruby
นาธานลอง

คำตอบ:


265

privateดูเหมือนจะไม่ทำงานหากคุณกำหนดวิธีการในวัตถุที่ชัดเจน (ในกรณีของคุณself) คุณสามารถใช้private_class_methodเพื่อกำหนดวิธีการเรียนเป็นส่วนตัว (หรือเช่นคุณอธิบาย)

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

อีกวิธีหนึ่ง (ใน ruby ​​2.1+) เนื่องจากนิยามเมธอดส่งคืนสัญลักษณ์ของชื่อเมธอดคุณยังสามารถใช้สิ่งต่อไปนี้:

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

105

ExiReเขียนว่า:

พฤติกรรมของทับทิมเช่นนี้น่าผิดหวังจริงๆ ฉันหมายความว่าถ้าคุณย้ายไปที่ส่วนส่วนตัวด้วยตนเองวิธีการนั้นจะไม่ส่วนตัว แต่ถ้าคุณย้ายไปเรียนด้วยตนเอง << คลาสมันก็ใช้งานได้ มันน่าขยะแขยง

อาจทำให้สับสนได้ แต่ก็น่าผิดหวัง แต่ก็ไม่ได้น่าขยะแขยง

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

ในการกำหนดเมธอดinstanceส่วนตัวคุณเรียกprivateใช้คลาสของอินสแตนซ์เพื่อตั้งค่าการมองเห็นเริ่มต้นสำหรับเมธอดที่กำหนดไว้ในภายหลังเป็นไพรเวต ... และด้วยเหตุนี้จึงเหมาะสมอย่างยิ่งที่จะกำหนดเมธอดคลาสส่วนตัวโดยการเรียกprivateคลาสของคลาสนั้น ๆ metaclass ของมัน

หลักอื่น ๆ ภาษา OO ที่ประกาศตัวเองอาจทำให้คุณสับสนน้อยลง แต่คุณแลกเปลี่ยนรูปแบบของวัตถุที่สับสนและไม่สอดคล้องกัน(ไม่สอดคล้องกัน)โดยไม่มีอำนาจในการอำนวยความสะดวกในการเปรียบเทียบของ Ruby


ดังนั้นถ้าฉันเข้าใจถูกต้องทับทิมตัวเองไม่มีคีย์เวิร์ดตัวแก้ไขการเข้าถึง (พับลิกไพรเวตและได้รับการป้องกัน) แต่แทนที่จะมีเมธอดตัวแก้ไขการเข้าถึง (พับลิกไพรเวตการป้องกัน)? นี่เป็นสิ่งที่ควรนำมาใช้กับตัวติดตามบั๊ก ruby ​​สำหรับ Matz เพื่อใช้ตัวดัดแปลงการเข้าถึงคำหลักที่เหมาะสมหรือเป็นพฤติกรรมที่คาดหวังหรือไม่
Edward

13
@Edward มันออกแบบมาว่าวิธีการjunichiito.blogspot.co.uk/2012/03/... ทำไม "เหมาะสม"
1313

1
ต่อจากนี้แทนการทำคุณสามารถทำprivate_class_method :method_name private_class_method def method_name...
bjt38

send(private_method)นอกจากนี้ยังสามารถเข้าถึงได้นอกวัตถุ
stevenspiel

1
@ bjt38 เพียงเพื่อความกระจ่างก็จะเป็นprivate_class_method def self.method_name
ทอม

78

โดยค่าเริ่มต้นวิธีการเรียนทั้งหมดเป็นสาธารณะ หากต้องการทำให้เป็นส่วนตัวคุณสามารถใช้Module # private_class_methodเช่น @tjwallace เขียนหรือกำหนดให้แตกต่างกันตามที่คุณทำ:

class << self

  private

  def method_name
    ...
  end
end

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


17

เพื่อความสมบูรณ์เราสามารถหลีกเลี่ยงการประกาศ private_class_method ในบรรทัดแยกต่างหาก โดยส่วนตัวฉันไม่ชอบการใช้งานนี้ แต่ก็รู้ดีว่ามันมีอยู่จริง

private_class_method  def self.method_name
 ....
end

5

ฉันก็เช่นกันลองหา Ruby (หรืออย่างน้อยก็มีความรู้เกี่ยวกับมัน) ที่ขาดจากเครื่องหมายในบริเวณนี้ เช่นต่อไปนี้เป็นสิ่งที่ฉันต้องการ แต่เงอะงะ

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

ปัญหาของฉันกับรหัสข้างต้นคือความต้องการไวยากรณ์ของ Ruby และตัวชี้วัดคุณภาพของรหัสของฉันสมรู้ร่วมคิดที่จะทำให้รหัสที่ยุ่งยาก หากต้องการให้รหัสทำงานได้ตามที่ฉันต้องการและหยุดการวัดให้เงียบฉันต้องเปรียบเทียบ () วิธีการเรียน เนื่องจากฉันไม่ต้องการให้เป็นส่วนหนึ่งของ API สาธารณะ 'คลาสฉันจึงต้องให้เป็นส่วนตัว แต่' ส่วนตัว 'ด้วยตัวเองไม่ทำงาน แต่ฉันถูกบังคับให้ใช้ 'private_class_method' หรือการแก้ไขเช่นนี้ ในทางกลับกันบังคับให้ใช้ 'self.class.send (: เปรียบเทียบ ... ' สำหรับตัวแปรแต่ละตัวที่ฉันทดสอบใน '== ()' ตอนนี้นั่นเป็นเรื่องที่ค่อนข้างลำบาก


ความจริงที่ว่าคุณจำเป็นต้องใช้sendไม่มีส่วนเกี่ยวข้องกับ "วิธี" คุณทำเครื่องหมายวิธีการเรียนแบบส่วนตัว วิธีการส่วนตัวไม่สามารถเรียกจาก "นอก"
ปาสกาล

4

มีการกำหนดวิธีการอินสแตนซ์ภายในบล็อกคำจำกัดความของคลาส วิธีการเรียนถูกกำหนดเป็นวิธีการเดี่ยวในชั้นเรียนเดี่ยวของชั้นที่รู้จักกันอย่างไม่เป็นทางการว่า "metaclass" หรือ "eigenclass" privateไม่ใช่คำหลัก แต่เป็นวิธีการ ( โมดูล # ส่วนตัว )

นี่คือการเรียกไปยังวิธีการself#private/ A#privateที่ "สลับ" การเข้าถึงแบบส่วนตัวบนสำหรับคำจำกัดความวิธีการของอินสแตนซ์ที่กำลังจะมาถึงจนกว่าจะสลับเป็นอย่างอื่น:

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

ตามที่ระบุไว้ก่อนหน้านี้วิธีการเรียนเป็นวิธีการเดี่ยวที่กำหนดไว้ในระดับชั้นเดียว

def A.class_method; end

หรือใช้ไวยากรณ์พิเศษเพื่อเปิดเนื้อหาคำจำกัดความของคลาส singleton แบบไม่ระบุชื่อของ A:

class << A
  def class_method; end
end

ผู้รับของ "ข้อความส่วนตัว" - self -inside class Aเป็นวัตถุคลาส A. ตนเองภายในclass << Aบล็อกเป็นวัตถุอื่นชั้นเดียว

ตัวอย่างต่อไปนี้เป็นการโทรจริงสองวิธีที่แตกต่างกันที่เรียกว่าส่วนตัวโดยใช้ผู้รับหรือเป้าหมายที่แตกต่างกันสองคนสำหรับการโทร ในส่วนแรกเรากำหนดวิธีการอินสแตนซ์ส่วนตัว ("ในคลาส A") ในส่วนหลังเรากำหนดวิธีการเรียนส่วนตัว (ในความเป็นจริงเป็นวิธีการเดี่ยวในวัตถุชั้นเดี่ยวของ 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

ทีนี้ลองเขียนตัวอย่างนี้อีกหน่อย:

class A
  private
    def self.class_method; end
end

คุณเห็นความผิดพลาดที่ [นักออกแบบภาษา Ruby] ทำขึ้นมาหรือไม่? คุณสลับการเข้าถึงแบบส่วนตัวสำหรับวิธีการอินสแตนซ์ที่กำลังจะมาถึงทั้งหมดของ A แต่ดำเนินการประกาศวิธีการแบบซิงเกิลในคลาสที่แตกต่างนั่นคือคลาสซิงเกิล


-1

ดูเหมือนว่าทับทิมจะให้ทางออกที่ไม่ดี หากต้องการอธิบายให้เริ่มต้นด้วยตัวอย่างง่ายๆของ C ++ ที่แสดงการเข้าถึงวิธีการเรียนส่วนตัว:

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

ทำงานด้านบน

   % ./a.out
   instance method
   class method

ตอนนี้ Ruby ดูเหมือนจะไม่ให้สิ่งที่เทียบเท่ากัน ฉันคิดว่ากฎของรูบี้เป็นวิธีการส่วนตัวที่ต้องไม่เข้าถึงกับผู้รับ นั่นคือ,

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

ไม่เป็นไรสำหรับวิธีการอินสแตนซ์ส่วนตัว แต่ทำให้เกิดปัญหากับวิธีการเรียนส่วนตัว

ฉันต้องการทับทิมทำงานด้วยวิธีนี้:

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

แต่อนิจจาข้างต้นใช้งานไม่ได้ มีคนรู้วิธีที่ดีกว่านี้ไหม?

เมื่อฉันเห็น 'ส่ง' ก่อนวิธีการมันเป็นสัญญาณที่ชัดเจนว่ารหัสนั้นละเมิดเจตนาของนักออกแบบของ API แต่ในกรณีนี้การออกแบบนั้นมีวิธีการแบบเฉพาะของคลาสที่เรียกว่าคลาสส่วนตัว


-14

ตั้งแต่ทับทิม 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

private def self.second_methodก่อนหน้านี้ฉันเคยลองใช้วิธีนี้ก่อนซึ่งจะไม่ทำงานกับทับทิมของฉัน 2.3.3 แต่สัญกรณ์นี้เหมาะกับฉัน
Emile Vrijdags

11
สิ่งนี้ไม่ถูกต้องเนื่องจากการโทรCheck.second_methodจะทำงานได้อย่างไม่มีปัญหาดังนั้นจึงไม่เป็นส่วนตัว
Deiwin

1
ลองใช้งานไม่ได้private_class_method :second_method
KING SABRI
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.