ส่วนต่อประสาน Java เทียบเท่ากับอะไรใน Ruby?


106

เราสามารถเปิดเผยอินเทอร์เฟซใน Ruby เหมือนกับที่เราทำใน java และบังคับใช้โมดูลหรือคลาส Ruby เพื่อใช้วิธีการที่กำหนดโดยอินเตอร์เฟส

วิธีหนึ่งคือใช้การถ่ายทอดทางพันธุกรรมและ method_missing เพื่อให้ได้ผลเช่นเดียวกัน แต่มีแนวทางอื่นที่เหมาะสมกว่านี้หรือไม่



6
คุณควรถามตัวเองสองครั้งว่าทำไมคุณถึงต้องการสิ่งนี้ บ่อยครั้งที่มีการใช้อินเทอร์เฟซเพียงพอเพื่อรวบรวมสิ่งที่น่ารังเกียจซึ่งไม่ใช่ปัญหาในทับทิม
Arnis Lapsa

1
คำถามนี้อาจถือได้ว่าซ้ำกับ [ ใน Ruby อะไรที่เทียบเท่ากับอินเทอร์เฟซใน C #? ] ( StackOverflow.Com/q/3505521/#3507460 )
Jörg W Mittag

2
ทำไมฉันถึงต้องการสิ่งนี้? ฉันต้องการใช้บางสิ่งที่คุณสามารถเรียกว่า "versionable" ซึ่งทำให้เอกสาร / ไฟล์เป็นเวอร์ชันได้ แต่สามารถกำหนดเวอร์ชันได้โดยใช้อะไร .... เช่นฉันสามารถทำให้เป็นเวอร์ชันได้โดยใช้ซอฟต์แวร์พื้นที่เก็บข้อมูลที่มีอยู่เช่น SVN หรือ CVS ไม่ว่ากลไกใดก็ตามที่ฉันเลือกควรมีฟังก์ชันขั้นต่ำพื้นฐานบางอย่าง ฉันต้องการใช้อินเทอร์เฟซเหมือนกับสิ่งที่บังคับใช้การใช้งานฟังก์ชันขั้นต่ำที่เปลือยเปล่าเหล่านี้โดยการใช้งานที่เก็บข้อมูลพื้นฐานใหม่ ๆ
crazycrv

Sandi Metz ในหนังสือ POODR ของเธอใช้การทดสอบกับอินเทอร์เฟซเอกสาร มันคุ้มมากที่จะอ่านหนังสือเล่มนี้ ในปี 2015 ฉันจะบอกว่าคำตอบของ @ aleksander-pohl นั้นดีที่สุด
Greg Dan

คำตอบ:


85

Ruby มีอินเทอร์เฟซเหมือนกับภาษาอื่น ๆ

โปรดทราบว่าคุณต้องระวังไม่ให้คอนเซ็ปต์ของอินเทอร์เฟซซึ่งเป็นข้อกำหนดนามธรรมของความรับผิดชอบการค้ำประกันและโปรโตคอลของหน่วยที่มีแนวคิดinterfaceซึ่งเป็นคีย์เวิร์ดในการเขียนโปรแกรม Java, C # และ VB.NET ภาษา ใน Ruby เราใช้อดีตตลอดเวลา แต่อย่างหลังไม่มีอยู่จริง

เป็นสิ่งสำคัญมากในการแยกแยะทั้งสองอย่าง สิ่งที่สำคัญคืออินเทอร์เฟซไม่ใช่ไฟล์interface. interfaceบอกคุณไม่มีอะไรสวยมากประโยชน์ ไม่มีอะไรที่แสดงให้เห็นถึงสิ่งนี้ได้ดีไปกว่าอินเทอร์เฟซ markerใน Java ซึ่งเป็นอินเทอร์เฟซที่ไม่มีสมาชิกเลยเพียงแค่ดูjava.io.Serializableและjava.lang.Cloneable; ทั้งสองinterfaceหมายถึงสิ่งที่แตกต่างกันมากแต่ก็มีลายเซ็นเดียวกัน

ดังนั้นถ้าสองinterfaces ว่าสิ่งที่แตกต่างกันหมายถึงมีลายเซ็นเดียวกันสิ่งที่ว่าเป็นinterfaceแม้แต่รับประกันคุณ?

อีกตัวอย่างที่ดี:

package java.util;

interface List<E> implements Collection<E>, Iterable<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException;
}

อะไรคือสิ่งที่อินเตอร์เฟซของjava.util.List<E>.add?

  • ความยาวของคอลเลกชันไม่ลดลง
  • ไอเท็มทั้งหมดที่อยู่ในคอลเลกชั่นก่อนหน้านี้ยังคงอยู่ที่นั่น
  • ที่elementอยู่ในคอลเลกชัน

และสิ่งใดที่ปรากฏในช่องinterface? ไม่มี! ไม่มีสิ่งใดในสิ่งinterfaceที่บอกว่าAddต้องเพิ่มเมธอดเลย แต่ก็อาจลบองค์ประกอบออกจากคอลเล็กชันได้เช่นกัน

นี่คือการใช้งานที่ถูกต้องสมบูรณ์interface:

class MyCollection<E> implements java.util.List<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException {
        remove(element);
    }
}

อีกตัวอย่างหนึ่ง: ที่java.util.Set<E>จริงแล้วมันบอกว่ามันคือชุด ? ไม่มีที่ไหน! หรืออย่างแม่นยำมากขึ้นในเอกสาร เป็นภาษาอังกฤษ.

ในทุกกรณีinterfacesทั้งจาก Java และ. NET ข้อมูลที่เกี่ยวข้องทั้งหมดอยู่ในเอกสารจริง ๆ แล้วไม่ใช่ในประเภท ดังนั้นหากประเภทไม่ได้บอกอะไรที่น่าสนใจให้คุณเก็บไว้เลย? ทำไมไม่ยึดติดกับเอกสาร? และนั่นคือสิ่งที่ Ruby ทำ

โปรดทราบว่ามีภาษาอื่น ๆที่สามารถอธิบายอินเทอร์เฟซได้อย่างมีความหมาย แต่ภาษาเหล่านั้นมักจะไม่เรียกสร้างซึ่งอธิบายถึงการเชื่อมต่อ " interface" typeพวกเขาเรียกว่า ในภาษาการเขียนโปรแกรมที่พิมพ์ขึ้นด้วยกันตัวอย่างเช่นคุณสามารถแสดงคุณสมบัติที่sortฟังก์ชันส่งคืนคอลเลกชันที่มีความยาวเท่ากับต้นฉบับโดยทุกองค์ประกอบที่อยู่ในต้นฉบับจะอยู่ในคอลเล็กชันที่เรียงลำดับและไม่มีองค์ประกอบที่ใหญ่กว่า ปรากฏก่อนองค์ประกอบที่เล็กกว่า

ดังนั้นในระยะสั้น: ทับทิมไม่ได้เทียบเท่ากับ interfaceJava มันไม่แต่มีความเทียบเท่ากับ Java อินเตอร์เฟซและก็เหมือนกับในชวา: เอกสาร

นอกจากนี้เช่นเดียวกับใน Java สามารถใช้การทดสอบการยอมรับเพื่อระบุอินเทอร์เฟซได้เช่นกัน

โดยเฉพาะอย่างยิ่งใน Ruby นั้น อินเทอร์เฟซของออบเจ็กต์จะถูกกำหนดโดยสิ่งที่สามารถทำได้ไม่ใช่สิ่งที่classเป็นอยู่หรือสิ่งที่moduleมันผสมออบเจ็กต์ใด ๆ ที่มี<<เมธอดสามารถต่อท้ายได้ นี้จะเป็นประโยชน์อย่างมากในการทดสอบหน่วยที่คุณก็สามารถส่งผ่านในArrayหรือStringแทนที่จะเป็นความซับซ้อนมากขึ้นLoggerแม้ว่าArrayและLoggerไม่เปิดเผยชัดเจนนอกเหนือจากความจริงที่ว่าพวกเขาทั้งสองมีวิธีการที่เรียกว่าinterface<<

อีกตัวอย่างหนึ่งคือStringIOซึ่งดำเนินการเช่นเดียวกันอินเตอร์เฟซเป็นIOและทำให้ส่วนใหญ่ของอินเตอร์เฟซของFileแต่ไม่มีการแบ่งปันบรรพบุรุษร่วมกันใด ๆ Objectนอกเหนือจาก


284
ในขณะที่การอ่านที่ดีฉันไม่พบคำตอบที่เป็นประโยชน์ มันอ่านเหมือนวิทยานิพนธ์ว่าทำไมinterfaceไม่มีประโยชน์ไม่มีจุดที่จะใช้ มันจะง่ายกว่าที่จะพูดว่าทับทิมถูกพิมพ์แบบไดนามิกและมีจุดเน้นที่แตกต่างกันในใจและทำให้แนวคิดเช่น IOC ไม่จำเป็น / ไม่ต้องการ เป็นการเปลี่ยนแปลงที่ยากหากคุณคุ้นเคยกับการออกแบบตามสัญญา บางสิ่งบางอย่าง Rails อาจได้รับประโยชน์ซึ่งทีมงานหลักได้รับรู้ตามที่คุณเห็นในเวอร์ชันล่าสุด
goliatone

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

9
interface สร้างแน่นอนเฉพาะที่จำเป็นในการรักษาที่แตกต่างกันเป็นเหมือนกันในพิมพ์ statically ภาษาเดียวมรดก (เช่นการรักษาLinkedHashSetและArrayListทั้งสองเป็นCollection) มันมีอะไรที่สวยมากทำอย่างไรกับอินเตอร์เฟซที่เป็นคำตอบของการแสดงนี้ ทับทิมไม่ได้พิมพ์แบบคงที่จึงมีความจำเป็นที่จะต้องไม่มีการสร้าง
Esailija

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

13
อาร์กิวเมนต์ของคุณเกี่ยวกับความไม่ถูกต้องของอินเทอร์เฟซรายการโดยการอ้างถึงวิธีการที่ดำเนินการลบในฟังก์ชันที่เรียกว่า "add" เป็นตัวอย่างคลาสสิกของอาร์กิวเมนต์ไร้สาระของโฆษณา reductio โดยเฉพาะอย่างยิ่งเป็นไปได้ในทุกภาษา (รวมทับทิม) ที่จะเขียนวิธีการที่ทำสิ่งที่แตกต่างจากที่คาดไว้ นี่ไม่ใช่ข้อโต้แย้งที่ถูกต้องสำหรับ "อินเทอร์เฟซ" ซึ่งเป็นเพียงโค้ดที่ไม่ถูกต้อง
Justin Ohms

61

ลอง "ตัวอย่างที่แชร์" ของ rspec:

https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples

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

it_behaves_like "my interface"

ตัวอย่างที่สมบูรณ์:

RSpec.shared_examples "a collection" do
  describe "#size" do
    it "returns number of elements" do
      collection = described_class.new([7, 2, 4])
      expect(collection.size).to eq(3)
    end
  end
end

RSpec.describe Array do
  it_behaves_like "a collection"
end

RSpec.describe Set do
  it_behaves_like "a collection"
end

อัปเดต : แปดปีต่อมา (2020) ตอนนี้ทับทิมรองรับอินเทอร์เฟซที่พิมพ์แบบคงที่ผ่านเชอร์เบทแล้ว ดูคลาสนามธรรมและอินเทอร์เฟซในเอกสารเชอร์เบท


17
ฉันเชื่อว่านี่ควรเป็นคำตอบที่ได้รับการยอมรับ นี่เป็นวิธีที่ภาษาที่อ่อนแอส่วนใหญ่สามารถให้ Java เช่นอินเทอร์เฟซ สิ่งที่ได้รับการยอมรับอธิบายว่าเหตุใด Ruby จึงไม่มีอินเทอร์เฟซไม่ใช่วิธีการเลียนแบบ
SystematicFrank

1
ฉันยอมรับคำตอบนี้ช่วยฉันได้มากขึ้นในขณะที่นักพัฒนา Java ที่ย้ายมาใช้ Ruby มากกว่าคำตอบที่ยอมรับข้างต้น
แคม

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

ทับทิมทำให้ทุกอย่างเป็นไปในทางปฏิบัติ หากคุณต้องการมีเอกสารและเขียนโค้ดที่ดีให้เพิ่มการทดสอบ / ข้อกำหนดและนั่นจะเป็นการตรวจสอบการพิมพ์แบบคงที่
Dmitry Polushkin

42

เราสามารถเปิดเผยอินเทอร์เฟซใน Ruby เหมือนกับที่เราทำใน java และบังคับใช้โมดูลหรือคลาส Ruby เพื่อใช้วิธีการที่กำหนดโดยอินเตอร์เฟส

Ruby ไม่มีฟังก์ชันนั้น ในหลักการก็ไม่จำเป็นต้องให้เป็นทับทิมใช้สิ่งที่เรียกว่าเป็ดพิมพ์

มีแนวทางไม่กี่วิธีที่คุณสามารถทำได้

เขียนการใช้งานที่เพิ่มข้อยกเว้น หากคลาสย่อยพยายามใช้วิธีที่ไม่ได้นำไปใช้งานจะล้มเหลว

class CollectionInterface
  def add(something)
    raise 'not implemented'
  end
end

นอกจากนี้คุณควรเขียนโค้ดทดสอบที่บังคับใช้สัญญาของคุณ (โพสต์อื่นที่นี่เรียกอินเทอร์เฟซไม่ถูกต้อง)

หากคุณพบว่าตัวเองกำลังเขียนวิธีการโมฆะเหมือนข้างบนตลอดเวลาให้เขียนโมดูลตัวช่วยที่รวบรวมข้อมูลนั้น

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

class Collection
  extend Interface
  method :add
  method :remove
end

ตอนนี้รวมข้างต้นกับโมดูล Ruby และคุณใกล้เคียงกับสิ่งที่คุณต้องการ ...

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

module Collection
  extend Interface
  method :add
  method :remove
end

col = Collection.new # <-- fails, as it should

แล้วคุณสามารถทำได้

class MyCollection
  include Collection

  def add(thing)
    puts "Adding #{thing}"
  end
end

c1 = MyCollection.new
c1.add(1)     # <-- output 'Adding 1'
c1.remove(1)  # <-- fails with not implemented

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


โอเค แต่ถ้า Collection = MyCollection ของคุณใช้วิธีการที่ไม่ได้กำหนดไว้ในอินเทอร์เฟซสิ่งนี้จะทำงานได้อย่างสมบูรณ์ดังนั้นคุณจึงไม่สามารถมั่นใจได้ว่า Object ของคุณมีเฉพาะการกำหนดวิธีการเชื่อมต่อเท่านั้น
Joel AZEMAR

มันยอดเยี่ยมมากขอบคุณ การพิมพ์เป็ดเป็นเรื่องปกติ แต่บางครั้งก็เป็นการดีที่จะสื่อสารกับนักพัฒนาอื่น ๆ อย่างชัดเจนว่าอินเทอร์เฟซควรทำงานอย่างไร
Mirodinho

10

อย่างที่ทุกคนบอกไม่มีระบบอินเตอร์เฟสสำหรับทับทิม แต่ด้วยการวิปัสสนาคุณสามารถดำเนินการได้ด้วยตัวเองค่อนข้างง่าย นี่คือตัวอย่างง่ายๆที่สามารถปรับปรุงได้หลายวิธีเพื่อช่วยคุณเริ่มต้น:

class Object
  def interface(method_hash)
    obj = new
    method_hash.each do |k,v|
      if !obj.respond_to?(k) || !((instance_method(k).arity+1)*-1)
        raise NotImplementedError, "#{obj.class} must implement the method #{k} receiving #{v} parameters"
      end
    end
  end
end

class Person
  def work(one,two,three)
    one + two + three
  end

  def sleep
  end

  interface({:work => 3, :sleep => 0})
end

การลบหนึ่งในวิธีการที่ประกาศไว้ใน Person หรือเปลี่ยนจำนวนอาร์กิวเมนต์จะทำให้ a NotImplementedError.


5

ไม่มีสิ่งต่างๆเช่นอินเทอร์เฟซในทาง Java แต่ยังมีสิ่งอื่น ๆ ที่คุณสามารถเพลิดเพลินกับทับทิมได้

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

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

หากคุณต้องการขยายออบเจ็กต์หรือคลาส (สิ่งเดียวกันในทับทิม) โดยพฤติกรรมบางอย่างหรือค่อนข้างมีวิธีการสืบทอดหลายแบบให้ใช้กลไกincludeหรือ extendด้วยincludeคุณสามารถรวมวิธีการจากคลาสหรือโมดูลอื่นลงในวัตถุ ด้วยextendคุณสามารถเพิ่มพฤติกรรมให้กับคลาสเพื่อให้อินสแตนซ์มีวิธีการที่เพิ่มเข้ามา นั่นเป็นคำอธิบายสั้น ๆ

ฉันคิดว่าวิธีที่ดีที่สุดในการแก้ไขความต้องการของอินเทอร์เฟซ Java คือการทำความเข้าใจเกี่ยวกับโมเดลวัตถุทับทิม (ดูการบรรยายของ Dave Thomas ) คุณอาจลืมเกี่ยวกับอินเทอร์เฟซ Java หรือคุณมีแอปพลิเคชั่นพิเศษในกำหนดการของคุณ


การบรรยายของ Dave Thomas อยู่เบื้องหลัง paywall
Purplejacket

5

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

โดยปกติการทดสอบจะถูกกำหนดโดยการแยกโดยใช้ mocks และ Stubs แต่ยังมีเครื่องมือเช่นBogusซึ่งช่วยให้สามารถกำหนดการทดสอบสัญญาได้ การทดสอบดังกล่าวไม่เพียง แต่กำหนดลักษณะการทำงานของคลาส "หลัก" เท่านั้น แต่ยังตรวจสอบด้วยว่ามีวิธีการที่ถูกตรึงไว้ในคลาสที่ทำงานร่วมกัน

หากคุณกังวลเกี่ยวกับอินเทอร์เฟซใน Ruby จริงๆฉันขอแนะนำให้ใช้กรอบการทดสอบที่ดำเนินการทดสอบสัญญา


3

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

พิจารณาอินเทอร์เฟซของคุณด้วยวิธีการที่กำหนดไว้เช่นนั้น

class FooInterface
  class NotDefinedMethod < StandardError; end
  REQUIRED_METHODS = %i(foo).freeze
  def initialize(object)
    @object = object
    ensure_method_are_defined!
  end
  def method_missing(method, *args, &block)
    ensure_asking_for_defined_method!(method)
    @object.public_send(method, *args, &block)
  end
  private
  def ensure_method_are_defined!
    REQUIRED_METHODS.each do |method|
      if !@object.respond_to?(method)
        raise NotImplementedError, "#{@object.class} must implement the method #{method}"
      end
    end
  end
  def ensure_asking_for_defined_method!(method)
    unless REQUIRED_METHODS.include?(method)
      raise NotDefinedMethod, "#{method} doesn't belong to Interface definition"
    end
  end
end

จากนั้นคุณสามารถเขียนวัตถุโดยมีสัญญา Interface เป็นอย่างน้อย:

class FooImplementation
  def foo
    puts('foo')
  end
  def bar
    puts('bar')
  end
end

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

#  > FooInterface.new(FooImplementation.new).foo
# => foo

#  > FooInterface.new(FooImplementation.new).bar
# => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition

และคุณสามารถตรวจสอบให้แน่ใจว่า Object ของคุณใช้นิยามวิธีการเชื่อมต่อทั้งหมดของคุณ

class BadFooImplementation
end

#  > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo

2

ฉันได้ขยายคำตอบของ carlosayam สำหรับความต้องการเพิ่มเติมของฉัน นี้จะเพิ่มการบังคับใช้เพิ่มเติมคู่และตัวเลือกชั้นอินเตอร์เฟซ: required_variableและoptional_variableที่สนับสนุนค่าเริ่มต้น

ฉันไม่แน่ใจว่าคุณต้องการใช้โปรแกรมเมตานี้กับสิ่งที่ใหญ่เกินไป

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

ข้อแม้วิธีนี้จะทำให้เกิดข้อผิดพลาดในการเรียกรหัสเท่านั้น ยังคงต้องมีการทดสอบเพื่อบังคับใช้อย่างเหมาะสมก่อนรันไทม์

ตัวอย่างรหัส

interface.rb

module Interface
  def method(name)
    define_method(name) do
      raise "Interface method #{name} not implemented"
    end
  end

  def required_variable(name)
    define_method(name) do
      sub_class_var = instance_variable_get("@#{name}")
      throw "@#{name} must be defined" unless sub_class_var
      sub_class_var
    end
  end

  def optional_variable(name, default)
    define_method(name) do
      instance_variable_get("@#{name}") || default
    end
  end
end

plugin.rb

ฉันใช้ไลบรารีซิงเกิลตันสำหรับรูปแบบที่กำหนดที่ฉันใช้ ด้วยวิธีนี้คลาสย่อยใด ๆ จะสืบทอดไลบรารีซิงเกิลตันเมื่อใช้ "อินเทอร์เฟซ" นี้

require 'singleton'

class Plugin
  include Singleton

  class << self
    extend Interface

    required_variable(:name)
    required_variable(:description)
    optional_variable(:safe, false)
    optional_variable(:dependencies, [])

    method :run
  end
end

my_plugin.rb

สำหรับความต้องการของฉันสิ่งนี้ต้องการให้คลาสที่ใช้ "อินเทอร์เฟซ" คลาสย่อยนั้น

class MyPlugin < Plugin

  @name = 'My Plugin'
  @description = 'I am a plugin'
  @safe = true

  def self.run
    puts 'Do Stuff™'
  end
end

2

Ruby เองไม่มีอินเทอร์เฟซเทียบเท่าใน Java

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

เรียกว่าclass_interface.

มันใช้งานได้ค่อนข้างเรียบง่าย ก่อนติดตั้งอัญมณีโดยgem install class_interfaceหรือเพิ่มไปยัง Gemfile และ bundle installrund

การกำหนดอินเทอร์เฟซ:

require 'class_interface'

class IExample
  MIN_AGE = Integer
  DEFAULT_ENV = String
  SOME_CONSTANT = nil

  def self.some_static_method
  end

  def some_instance_method
  end
end

การติดตั้งอินเทอร์เฟซนั้น:

class MyImplementation
  MIN_AGE = 21
  DEFAULT_ENV = 'dev' 
  SOME_CONSTANT = 'some_value'

  def specific_method
    puts "very specific"
  end

  def self.some_static_method
    puts "static method is implemented!"
  end

  def some_instance_method
    # implementation
  end

  def self.another_methods
    # implementation
  end

  implements IExample
end

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

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

เพิ่มเติมได้ที่: https://github.com/magynhard/class_interface


0

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

require 'playable' 

class Instrument 
  implements Playable
end

Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable

มันไม่ได้ตรวจสอบข้อโต้แย้งวิธี 0.2.0มันจะเป็นของรุ่น ตัวอย่างรายละเอียดเพิ่มเติมที่https://github.com/bluegod/rint

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