Können wir Schnittstellen in Ruby wie in Java verfügbar machen und die Ruby-Module oder -Klassen erzwingen, um die durch die Schnittstelle definierten Methoden zu implementieren?
Eine Möglichkeit besteht darin, Vererbung und method_missing zu verwenden, um dasselbe zu erreichen. Gibt es jedoch einen anderen geeigneteren Ansatz?
Antworten:
Ruby hat Schnittstellen wie jede andere Sprache.
Beachten Sie, dass Sie darauf achten müssen, das Konzept der Schnittstelle , bei der es sich um eine abstrakte Spezifikation der Verantwortlichkeiten, Garantien und Protokolle einer Einheit handelt, nicht mit dem Konzept des
interface
Schlüsselworts in der Java-, C # - und VB.NET-Programmierung zu verknüpfen Sprachen. In Ruby verwenden wir das erstere ständig, aber das letztere existiert einfach nicht.Es ist sehr wichtig, die beiden zu unterscheiden. Wichtig ist das Interface , nicht das
interface
. Dasinterface
sagt dir so ziemlich nichts Nützliches. Nichts zeigt dies besser als die Marker-Schnittstellen in Java, bei denen es sich um Schnittstellen handelt, die überhaupt keine Mitglieder haben: Schauen Sie sich einfachjava.io.Serializable
und anjava.lang.Cloneable
. Diese beideninterface
bedeuten sehr unterschiedliche Dinge, haben aber genau die gleiche Signatur.Also, wenn zwei
interface
s , dass verschiedene Dinge bedeuten, die gleiche Signatur haben, was genau das istinterface
auch Sie garantiert?Ein weiteres gutes Beispiel:
Was ist die Schnittstelle von
java.util.List<E>.add
?element
ist in der SammlungUnd welche davon taucht tatsächlich in der auf
interface
? Keiner! Es gibt nichts in deminterface
, was besagt, dass dieAdd
Methode überhaupt etwas hinzufügen muss , sie könnte genauso gut ein Element aus der Sammlung entfernen .Dies ist eine absolut gültige Implementierung davon
interface
:Ein weiteres Beispiel: Wo
java.util.Set<E>
steht eigentlich, dass es sich um eine Menge handelt ? Nirgends! Oder genauer gesagt in der Dokumentation. Auf Englisch.In so ziemlich allen Fällen
interfaces
von Java und .NET befinden sich alle relevanten Informationen tatsächlich in den Dokumenten, nicht in den Typen. Also, wenn die Typen Ihnen sowieso nichts Interessantes sagen, warum sollten Sie sie überhaupt behalten? Warum nicht einfach bei der Dokumentation bleiben? Und genau das macht Ruby.Beachten Sie, dass es andere Sprachen gibt, in denen die Schnittstelle tatsächlich auf sinnvolle Weise beschrieben werden kann. Diese Sprachen rufen jedoch normalerweise nicht das Konstrukt auf, das die Schnittstelle "
interface
" beschreibt, sondern sie nennen estype
. In einer abhängig typisierten Programmiersprache können Sie beispielsweise die Eigenschaften ausdrücken, dass einesort
Funktion eine Sammlung mit der gleichen Länge wie das Original zurückgibt, dass jedes Element im Original auch in der sortierten Sammlung enthalten ist und dass kein größeres Element vorhanden ist erscheint vor einem kleineren Element.Kurz gesagt: Ruby hat kein Äquivalent zu Java
interface
. Es entspricht jedoch einer Java- Schnittstelle und ist genau das gleiche wie in der Java: -Dokumentation.Ebenso wie in Java können Akzeptanztests auch zum Angeben von Schnittstellen verwendet werden.
Insbesondere in Ruby, die Schnittstelle ist eines Objekts bestimmt durch das, was er kann tun , nicht das, was
class
ist, oder wasmodule
sie mischt in. Jedes Objekt , das eine hat<<
Methode kann angehängt werden. Dies ist sehr nützlich bei Unit-Tests, bei denen Sie einfach einArray
oder einString
statt eines komplizierteren bestehen könnenLogger
, obwohlArray
undLogger
teilen nicht eine expliziteinterface
abgesehen von der Tatsache , dass sie beide haben eine Methode aufgerufen<<
.Ein anderes Beispiel ist
StringIO
, dass dieselbe Schnittstelle wieIO
und damit ein großer Teil der Schnittstelle von implementiertFile
wird, ohne jedoch einen gemeinsamen Vorfahren zu teilenObject
.quelle
interface
es nutzlos ist und den Sinn seiner Verwendung verfehlt. Es wäre einfacher gewesen zu sagen, dass Ruby dynamisch typisiert ist und einen anderen Fokus hat und Konzepte wie IOC unnötig / unerwünscht machen. Es ist eine schwierige Aufgabe, wenn Sie an Design by Contract gewöhnt sind. Davon könnte Rails profitieren, was das Kernteam erkannt hat, wie Sie in den neuesten Versionen sehen können.interface
möglicherweise nicht alle relevanten Informationen, bietet jedoch einen offensichtlichen Platz für die Dokumentation. Ich habe eine Klasse in Ruby geschrieben, die (genug von) IO implementiert, aber ich habe es durch Ausprobieren gemacht und war mit dem Prozess nicht allzu zufrieden. Ich habe auch mehrere Implementierungen einer eigenen Schnittstelle geschrieben, aber es erwies sich als Herausforderung, zu dokumentieren, welche Methoden erforderlich sind und was sie tun sollen, damit andere Mitglieder meines Teams Implementierungen erstellen können.interface
Konstrukt wird in der Tat nur benötigt, um verschiedene Typen in statisch typisierten Einzelvererbungssprachen (z. B. behandelnLinkedHashSet
undArrayList
beides als aCollection
) als gleich zu behandeln. Es hat so ziemlich nichts mit Interface zu tun, wie diese Antwort zeigt. Ruby ist nicht statisch typisiert, daher ist das Konstrukt nicht erforderlich .Probieren Sie die "gemeinsamen Beispiele" von rspec aus:
https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples
Sie schreiben eine Spezifikation für Ihre Schnittstelle und fügen dann eine Zeile in die Spezifikation jedes Implementierers ein, z.
Vollständiges Beispiel:
Update : Acht Jahre später (2020) unterstützt Ruby nun statisch typisierte Schnittstellen über Sorbet. Siehe Abstrakte Klassen und Schnittstellen in den Sorbet-Dokumenten.
quelle
Ruby verfügt nicht über diese Funktionalität. Im Prinzip werden sie nicht benötigt, da Ruby die sogenannte Ententypisierung verwendet .
Es gibt nur wenige Ansätze, die Sie verfolgen können.
Schreiben Sie Implementierungen, die Ausnahmen auslösen. Wenn eine Unterklasse versucht, die nicht implementierte Methode zu verwenden, schlägt dies fehl
Zusammen mit dem oben genannten sollten Sie Testcode schreiben, der Ihre Verträge erzwingt (welcher andere Beitrag hier fälschlicherweise Schnittstelle nennt )
Wenn Sie wie immer leere Methoden schreiben, schreiben Sie ein Hilfsmodul, das dies erfasst
Kombinieren Sie nun das Obige mit Ruby-Modulen und Sie sind nah an dem, was Sie wollen ...
Und dann kannst du es tun
Lassen Sie mich noch einmal betonen: Dies ist rudimentär, da alles in Ruby zur Laufzeit geschieht. Es erfolgt keine Überprüfung der Kompilierungszeit. Wenn Sie dies mit dem Testen verbinden, sollten Sie in der Lage sein, Fehler zu erkennen. Wenn Sie das oben Gesagte weiter ausführen, können Sie möglicherweise eine Schnittstelle schreiben , die beim ersten Erstellen eines Objekts dieser Klasse eine Überprüfung der Klasse durchführt. Machen Sie Ihre Tests so einfach wie das Anrufen
MyCollection.new
... ja, übertrieben :)quelle
Wie alle hier sagten, gibt es kein Schnittstellensystem für Ruby. Aber durch Selbstbeobachtung können Sie es ganz einfach selbst implementieren. Hier ist ein einfaches Beispiel, das auf viele Arten verbessert werden kann, um Ihnen den Einstieg zu erleichtern:
Wenn Sie eine der in Person deklarierten Methoden entfernen oder die Anzahl der Argumente ändern, wird a ausgelöst
NotImplementedError
.quelle
Es gibt keine Schnittstellen auf Java-Weise. Aber es gibt noch andere Dinge, die Sie in Rubin genießen können.
Wenn Sie eine Art von Typen und Schnittstellen implementieren möchten, damit die Objekte überprüft werden können, ob sie einige Methoden / Nachrichten enthalten, die Sie von ihnen benötigen, können Sie sich Rubycontracts ansehen . Es definiert einen Mechanismus ähnlich den PyProtocols . Ein Blog über die Typprüfung in Ruby ist hier .
Bei den genannten Ansätzen handelt es sich nicht um lebende Projekte, obwohl das Ziel zunächst gut zu sein scheint, scheinen die meisten Ruby-Entwickler ohne strenge Typprüfung leben zu können. Die Flexibilität von Ruby ermöglicht jedoch die Implementierung der Typprüfung.
Wenn Sie Objekte oder Klassen (dasselbe in Ruby) um bestimmte Verhaltensweisen erweitern möchten oder die Ruby-Methode der Mehrfachvererbung haben möchten, verwenden Sie den Mechanismus
include
oderextend
. Mit könneninclude
Sie Methoden aus einer anderen Klasse oder einem anderen Modul in ein Objekt aufnehmen. Mitextend
können Sie einer Klasse Verhalten hinzufügen, sodass ihre Instanzen die hinzugefügten Methoden haben. Das war jedoch eine sehr kurze Erklärung.Ich bin der Meinung, dass der beste Weg, um die Notwendigkeit der Java-Schnittstelle zu lösen, darin besteht, das Ruby-Objektmodell zu verstehen (siehe beispielsweise Dave Thomas-Vorlesungen ). Wahrscheinlich werden Sie Java-Schnittstellen vergessen. Oder Sie haben eine außergewöhnliche Bewerbung in Ihrem Zeitplan.
quelle
Wie viele Antworten zeigen, gibt es in Ruby keine Möglichkeit, eine Klasse zu zwingen, eine bestimmte Methode zu implementieren, indem sie von einer Klasse, einschließlich eines Moduls , erbt oder ähnlichem . Der Grund dafür ist wahrscheinlich die Verbreitung von TDD in der Ruby-Community, die eine andere Art der Definition der Schnittstelle darstellt. Die Tests geben nicht nur die Signaturen der Methoden an, sondern auch das Verhalten. Wenn Sie also eine andere Klasse implementieren möchten, die eine bereits definierte Schnittstelle implementiert, müssen Sie sicherstellen, dass alle Tests erfolgreich sind.
Normalerweise werden die Tests isoliert mithilfe von Mocks und Stubs definiert. Es gibt aber auch Tools wie Bogus , mit denen Vertragstests definiert werden können. Solche Tests definieren nicht nur das Verhalten der "primären" Klasse, sondern prüfen auch, ob die Stubbed-Methoden in den kooperierenden Klassen vorhanden sind.
Wenn Sie sich wirklich mit Schnittstellen in Ruby befassen, würde ich die Verwendung eines Testframeworks empfehlen, das Vertragstests implementiert.
quelle
Alle Beispiele hier sind interessant, aber es fehlt die Validierung des Schnittstellenvertrags. Ich meine, wenn Sie möchten, dass Ihr Objekt alle Definitionen der Schnittstellenmethoden implementiert und nur diese, die Sie nicht können. Deshalb schlage ich Ihnen ein schnelles einfaches Beispiel vor (das mit Sicherheit verbessert werden kann), um sicherzustellen, dass Sie genau das haben, was Sie von Ihrer Schnittstelle erwarten (Der Vertrag).
Betrachten Sie Ihre Schnittstelle mit den definierten Methoden wie diesen
Dann können Sie ein Objekt mit mindestens dem Schnittstellenvertrag schreiben:
Sie können Ihr Objekt sicher über Ihre Schnittstelle aufrufen, um sicherzustellen, dass Sie genau das sind, was die Schnittstelle definiert
Sie können auch sicherstellen, dass Ihr Objekt alle Definitionen Ihrer Schnittstellenmethoden implementiert
quelle
Ich habe die Antwort von Carlosayam für meine zusätzlichen Bedürfnisse etwas erweitert. Dies fügt ein paar zusätzliche Verstärkungen und Optionen auf die Klasse - Schnittstelle:
required_variable
undoptional_variable
das unterstützt einen Standardwert.Ich bin mir nicht sicher, ob Sie diese Metaprogrammierung mit etwas zu Großem verwenden möchten.
Wie in anderen Antworten angegeben, schreiben Sie am besten Tests, die das Gesuchte ordnungsgemäß durchsetzen, insbesondere wenn Sie mit der Durchsetzung von Parametern und Rückgabewerten beginnen möchten.
Vorsichtsmaßnahme Diese Methode löst nur beim Aufrufen des Codes einen Fehler aus. Für die ordnungsgemäße Durchsetzung vor der Laufzeit wären noch Tests erforderlich.
Codebeispiel
interface.rb
plugin.rb
Ich habe die Singleton-Bibliothek für das angegebene Muster verwendet, das ich verwende. Auf diese Weise erben alle Unterklassen die Singleton-Bibliothek, wenn diese "Schnittstelle" implementiert wird.
my_plugin.rb
Für meine Bedürfnisse erfordert dies, dass die Klasse, die die "Schnittstelle" implementiert, sie in Unterklassen unterteilt.
quelle
Ruby selbst hat keine exakte Entsprechung zu Schnittstellen in Java.
Da eine solche Schnittstelle jedoch manchmal sehr nützlich sein kann, habe ich selbst ein Juwel für Ruby entwickelt, das Java-Schnittstellen auf sehr einfache Weise emuliert.
Es heißt
class_interface
.Es funktioniert ganz einfach. Installiere zuerst den Edelstein von
gem install class_interface
oder füge ihn deiner Gemfile hinzu und rundebundle install
.Schnittstelle definieren:
Implementierung dieser Schnittstelle:
Wenn Sie eine bestimmte Konstante oder Methode nicht implementieren oder die Parameternummer nicht übereinstimmt, wird ein entsprechender Fehler ausgelöst, bevor das Ruby-Programm ausgeführt wird. Sie können den Typ der Konstanten sogar bestimmen, indem Sie einen Typ in der Schnittstelle zuweisen. Wenn null, ist jeder Typ zulässig.
Die Methode "implementiert" muss in der letzten Zeile einer Klasse aufgerufen werden, da dies die Codeposition ist, an der die oben implementierten Methoden bereits überprüft wurden.
Mehr unter: https://github.com/magynhard/class_interface
quelle
Ich stellte fest, dass ich das Muster "Nicht implementierter Fehler" zu oft für Sicherheitsüberprüfungen von Objekten verwendete, für die ich ein bestimmtes Verhalten wünschte. Am Ende schrieb ich einen Edelstein, der es grundsätzlich erlaubt, eine Schnittstelle wie diese zu verwenden:
Es wird nicht nach Methodenargumenten gesucht. Es funktioniert ab Version0.2.0
. Ausführlicheres Beispiel unter https://github.com/bluegod/rintquelle