ควรใช้คลาสที่ซ้อนและคลาสที่ซ้อนในโมดูลหรือไม่


144

ฉันค่อนข้างคุ้นเคยกับการใช้คลาสย่อยและโมดูล แต่เมื่อเร็ว ๆ นี้ฉันได้เห็นคลาสที่ซ้อนกันเช่นนี้

class Foo
  class Bar
    # do some useful things
  end
end

เช่นเดียวกับคลาสที่ซ้อนกันในโมดูลเช่น:

module Baz
  class Quux
    # more code
  end
end

ทั้งเอกสารและบทความกระจัดกระจายหรือฉันไม่ได้รับการศึกษาในเรื่องเพียงพอที่จะคลานหาคำค้นหาที่ถูกต้อง แต่ดูเหมือนว่าฉันไม่สามารถหาข้อมูลจำนวนมากในหัวข้อ

ใครสามารถให้ตัวอย่างหรือลิงค์ไปยังโพสต์ว่าทำไม / เมื่อจะใช้เทคนิคเหล่านั้น

คำตอบ:


140

ภาษา OOP อื่น ๆ มีคลาสภายในซึ่งไม่สามารถสร้างอินสแตนซ์ได้ ตัวอย่างเช่นใน Java

class Car {
    class Wheel { }
}

วิธีการในCarชั้นเรียนเท่านั้นที่สามารถสร้างWheels

ทับทิมไม่มีพฤติกรรมแบบนั้น

ในทับทิม

class Car
  class Wheel
  end
end

แตกต่างจาก

class Car
end

class Wheel
end

เฉพาะในชื่อของชั้นกับWheel Car::Wheelความแตกต่างในชื่อนี้สามารถทำให้โปรแกรมเมอร์อย่างชัดเจนว่าCar::Wheelคลาสสามารถแสดงล้อรถได้เท่านั้นซึ่งต่างจากล้อทั่วไป คำจำกัดความของคลาสการซ้อนใน Ruby เป็นเรื่องของการตั้งค่า แต่มันมีจุดประสงค์ในแง่ที่ว่ายิ่งบังคับใช้สัญญาระหว่างทั้งสองคลาสอย่างชัดเจน

แต่สำหรับล่าม Ruby มันมีความแตกต่างในชื่อเท่านั้น

สำหรับการสังเกตที่สองของคุณคลาสที่ซ้อนกันภายในของโมดูลมักใช้เพื่อกำหนดเนมสเปซของคลาส ตัวอย่างเช่น

module ActiveRecord
  class Base
  end
end

แตกต่างจาก

module ActionMailer
  class Base
  end
end

แม้ว่านี่จะไม่ใช่การใช้คลาสที่ซ้อนกันภายในโมดูลเท่านั้น แต่โดยทั่วไปจะเป็นคลาสที่พบได้บ่อยที่สุด


5
@rubyprince ผมไม่แน่ใจว่าสิ่งที่คุณหมายถึงการสร้างความสัมพันธ์ระหว่างและCar.new Car::Wheel.newแน่นอนคุณไม่จำเป็นต้องเริ่มต้นCarวัตถุเพื่อเริ่มต้นCar::Wheelวัตถุใน Ruby แต่Carชั้นจะต้องโหลดและดำเนินการเพื่อCar::Wheelให้สามารถใช้งานได้
Pan Thomakos

30
@Pan คุณสับสนคลาสภายในของ Java และคลาส Ruby namespaced คลาสที่ซ้อนกันของ Java แบบไม่คงที่เรียกว่าคลาสภายในและมีอยู่ภายในอินสแตนซ์ของคลาสภายนอกเท่านั้น มีฟิลด์ที่ซ่อนอยู่ซึ่งอนุญาตการอ้างอิงภายนอก คลาสภายในของ Ruby เป็นเนมสเปซเพียงและไม่ "ผูก" กับคลาสที่ล้อมรอบด้วยวิธีใด ๆ มันเทียบเท่ากับคลาสJava static (ซ้อนกัน) ใช่คำตอบนั้นมีคะแนนโหวตเยอะ แต่มันไม่ถูกต้องสมบูรณ์
DigitalRoss

7
ฉันไม่รู้เลยว่าคำตอบนี้ได้เพิ่มขึ้น 60 อัพอย่างไรนับประสาได้รับการยอมรับจาก OP ที่นี่ไม่มีคำแถลงที่แท้จริงเพียงคำเดียวในที่นี้ Ruby ไม่มีคลาสที่ซ้อนกันอย่าง Beta หรือ Newspeak มีอย่างไม่มีความสัมพันธ์ใด ๆ ระหว่างและCar Car::WheelModules (และคลาส) นั้นเป็นเพียง namespaces สำหรับค่าคงที่ไม่มีสิ่งเช่น class ซ้อนกันหรือโมดูลซ้อนใน Ruby
Jörg W Mittag

4
ความแตกต่างเพียงอย่างเดียวระหว่างสองคือความละเอียดคงที่ (ซึ่งเป็นคำศัพท์และทำให้แตกต่างอย่างเห็นได้ชัดเพราะทั้งสองตัวอย่างแตกต่างกันเล็กน้อย) อย่างไรก็ตามไม่มีความแตกต่างระหว่างสองอย่างที่เกี่ยวข้องกับชั้นเรียนที่เกี่ยวข้อง มีเพียงสองคลาสที่ไม่เกี่ยวข้องอย่างสมบูรณ์ ระยะเวลา Ruby ไม่มีคลาสซ้อน / ภายใน ความหมายของการเรียนที่ซ้อนกันถูกต้อง Car::Wheel.newแต่มันก็ไม่ได้นำไปใช้กับทับทิมที่คุณนิดสามารถทดสอบ: ความเจริญ ฉันเพิ่งสร้างWheelวัตถุที่ไม่ซ้อนในCarวัตถุ
Jörg W Mittag

10
โพสต์นี้ทำให้เข้าใจผิดอย่างมากโดยไม่ต้องอ่านความคิดเห็นทั้งหมด
นาธาน

50

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

คำตอบที่ยอมรับไม่ถูกต้องเกี่ยวกับอะไร 1ในตัวอย่างด้านล่างฉันสร้างอินสแตนซ์ของคลาสที่ล้อมรอบด้วย lexically โดยไม่มีอินสแตนซ์ของคลาสที่ล้อมรอบที่มีอยู่เดิม

class A; class B; end; end
A::B.new

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

การใช้คลาสแทนโมดูลเพื่อกำหนดเนมสเปซด้านนอกอาจสมเหตุสมผลในโปรแกรมหรือไฟล์เดียวหรือถ้าคุณใช้คลาสระดับบนสุดสำหรับบางสิ่งบางอย่างหรือถ้าคุณกำลังจะเพิ่มรหัสเพื่อเชื่อมโยงคลาสเข้าด้วยกัน ในสไตล์ชั้นในที่แท้จริง Ruby ไม่มีคลาสภายใน แต่ไม่มีอะไรทำให้คุณไม่สามารถสร้างพฤติกรรมที่เหมือนกันในโค้ด การอ้างอิงวัตถุด้านนอกจากวัตถุชั้นในจะยังคงต้องการจุดในอินสแตนซ์ของวัตถุภายนอก แต่การซ้อนคลาสจะแนะนำว่านี่คือสิ่งที่คุณอาจกำลังทำอยู่ โปรแกรมที่ถูกทำให้เป็นโมดูลอย่างระมัดระวังอาจสร้างคลาสที่ปิดล้อมไว้ก่อนเสมอ คุณไม่สามารถโทรหาnewโมดูล

คุณสามารถใช้รูปแบบทั่วไปแม้กระทั่งสคริปต์ซึ่งไม่จำเป็นต้องใช้เนมสเปซมากนักเพื่อความสนุกสนานและฝึกฝน ...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run

15
ได้โปรดได้โปรดอย่าเรียกชั้นนี้ว่า มันไม่ใช่. ชั้นBคือไม่ได้Aอยู่ภายในชั้นเรียน คง Bอยู่ในระดับ namespaced ภายในAแต่มีอย่างไม่มีความสัมพันธ์ระหว่างวัตถุอ้างอิงโดยB(ซึ่งในกรณีนี้เพิ่งเกิดขึ้นเป็นชั้นเรียน) Aและชั้นอ้างอิงโดย
Jörg W Mittag

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

1
สำหรับฉันมันเป็นคำสั่งนี้ที่ช่วยให้ฉันได้มากที่สุดเมื่อตัดสินใจระหว่างโมดูลและคลาส: You can't call new on a module.- ดังนั้นในแง่พื้นฐานถ้าฉันต้องการเนมสเปซบางคลาสและไม่จำเป็นต้องสร้างอินสแตนซ์ของ "คลาส" ภายนอกจริง ๆฉันจะใช้โมดูลด้านนอก แต่ถ้าฉันต้องการยกตัวอย่างอินสแตนซ์ของคลาส "การตัด / ด้านนอก" ฉันจะทำให้เป็นคลาสแทนโมดูล อย่างน้อยมันก็สมเหตุสมผลสำหรับฉัน
FireDragon

@FireDragon หรือกรณีการใช้งานอื่นอาจเป็นเพราะคุณต้องการคลาสที่เป็น Factory ซึ่งคลาสย่อยสืบทอดมาและโรงงานของคุณมีหน้าที่สร้างอินสแตนซ์ของคลาสย่อย ในสถานการณ์นั้นโรงงานของคุณไม่สามารถเป็นโมดูลได้เนื่องจากคุณไม่สามารถสืบทอดจากโมดูลดังนั้นจึงเป็นคลาสแม่ที่คุณไม่ได้สร้างอินสแตนซ์ (และสามารถ 'เนมสเปซ' เป็นลูกถ้าคุณต้องการเช่นโมดูล)
rmcsharry

15

คุณอาจต้องการใช้สิ่งนี้เพื่อจัดกลุ่มคลาสของคุณเป็นโมดูล เรียงลำดับสิ่งเนมสเปซ

ตัวอย่างเช่นอัญมณี Twitterใช้ namespaces เพื่อให้ได้สิ่งนี้:

Twitter::Client.new

Twitter::Search.new

ดังนั้นทั้งชั้นเรียนClientและSearchอาศัยอยู่ภายใต้Twitterโมดูล

หากคุณต้องการที่จะตรวจสอบแหล่งที่มารหัสสำหรับการเรียนทั้งสองสามารถพบได้ที่นี่และที่นี่

หวังว่านี่จะช่วยได้!


3
ลิงค์ Twitter อัพเดตอัญมณี: github.com/sferik/twitter/tree/master/lib/twitter
รหัส

6

ยังมีข้อแตกต่างอื่น ๆ ระหว่างคลาสที่ซ้อนกันและโมดูลที่ซ้อนอยู่ใน Ruby ก่อนหน้า 2.5 ซึ่งคำตอบอื่น ๆ ล้มเหลวที่จะกล่าวถึงที่ฉันรู้สึกว่าต้องพูดถึงที่นี่ มันเป็นกระบวนการค้นหา

กล่าวโดยย่อ: เนื่องจากการค้นหาค่าคงที่ระดับสูงสุดใน Ruby ก่อนหน้า 2.5, Ruby อาจสิ้นสุดการค้นหาคลาสที่ซ้อนกันของคุณในตำแหน่งที่ไม่ถูกต้อง ( Objectโดยเฉพาะอย่างยิ่ง) หากคุณใช้คลาสที่ซ้อนกัน

ในรูบีก่อน 2.5:
โครงสร้างชั้นที่ซ้อนกัน: สมมติว่าคุณได้เรียนXกับชั้นซ้อนกันหรือY X::Yและจากนั้นคุณมีระดับชั้นบนสุดชื่อYด้วย ถ้าX::Yไม่โหลดแล้วต่อไปนี้เกิดขึ้นเมื่อคุณโทรX::Y:

ต้องไม่พบYในทับทิมจะพยายามที่จะมองมันได้ในบรรพบุรุษของX Xตั้งแต่X[Object, Kernel, BasicObject]เป็นชั้นและไม่โมดูลก็มีบรรพบุรุษในระหว่างที่ ดังนั้นจึงพยายามที่จะมองหาYในObjectที่ที่มันพบว่ามันประสบความสำเร็จ

แต่มันเป็นระดับด้านบนและไม่ได้Y X::Yคุณจะได้รับคำเตือนนี้:

warning: toplevel constant Y referenced by X::Y


โครงสร้างโมดูลแบบซ้อน: สมมติในตัวอย่างก่อนหน้าXนี้ว่าเป็นโมดูลและไม่ใช่คลาส

โมดูลมีเพียงตัวเองเป็นบรรพบุรุษ: จะผลิตX.ancestors ในกรณีนี้ทับทิมจะไม่สามารถที่จะมองหาในหนึ่งของบรรพบุรุษของและจะโยน Rails (หรือเฟรมเวิร์กอื่น ๆ ที่มีการโหลดอัตโนมัติ) จะพยายามโหลดหลังจากนั้น ดูบทความนี้สำหรับข้อมูลเพิ่มเติม: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/ In Ruby 2.5: การค้นหาค่าคงที่ระดับสูงสุดถูกลบออก คุณสามารถใช้คลาสที่ซ้อนกันได้โดยไม่ต้องกลัวว่าจะเจอบั๊กนี้[X]

YXNameErrorX::Y






3

นอกเหนือจากคำตอบก่อนหน้า: โมดูลใน Ruby เป็นคลาส

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object

11
คุณต้องพูดให้ถูกกว่าว่า 'class ใน Ruby เป็นโมดูล'!
ทิม Diggins

2
ทุกอย่างใน Ruby อาจเป็นวัตถุ แต่โมดูลที่เรียกคลาสดูเหมือนจะไม่ถูกต้อง: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, เคอร์เนล, วัตถุ, โมดูล, คลาส]
Chad M

และที่นี่เรามานิยามของ "is"
ahnbizcad

โปรดทราบว่าโมดูลไม่สามารถสร้างอินสแตนซ์ได้ นั่นคือมันเป็นไปไม่ได้ที่จะสร้างวัตถุจากโมดูล newดังนั้นโมดูลซึ่งแตกต่างจากการเรียนไม่ได้มีวิธีการ ดังนั้นแม้ว่าคุณจะสามารถพูดได้ว่า Modules เป็นคลาส (ตัวพิมพ์เล็ก) แต่มันก็ไม่เหมือนกับ Class (ตัวพิมพ์ใหญ่) :)
rmcsharry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.