Czy możemy ujawnić interfejsy w Rubim, tak jak robimy to w Javie i wymusić na modułach lub klasach Rubiego implementację metod zdefiniowanych przez interfejs.
Jednym ze sposobów jest użycie dziedziczenia i braku metody, aby osiągnąć to samo, ale czy jest dostępne inne, bardziej odpowiednie podejście?
Odpowiedzi:
Ruby ma interfejsy jak każdy inny język.
Zauważ, że musisz uważać, aby nie pomylić koncepcji interfejsu , która jest abstrakcyjną specyfikacją obowiązków, gwarancji i protokołów jednostki z koncepcją,
interface
która jest słowem kluczowym w programowaniu Java, C # i VB.NET Języki. W Rubim cały czas używamy tego pierwszego, ale drugiego po prostu nie ma.Rozróżnienie tych dwóch jest bardzo ważne. Ważny jest interfejs , a nie
interface
.interface
Powie Ci prawie nic pożytecznego. Nic nie pokazuje tego lepiej niż interfejsy znaczników w Javie, które są interfejsami, które nie mają żadnych elementów członkowskich: wystarczy spojrzeć najava.io.Serializable
ijava.lang.Cloneable
; te dwainterface
oznaczają bardzo różne rzeczy, ale mają dokładnie ten sam podpis.Tak więc, jeśli dwie
interface
s, które mają różne znaczenie mają ten sam podpis, co dokładnie jestinterface
nawet gwarantując ci?Kolejny dobry przykład:
Co to jest interfejs z
java.util.List<E>.add
?element
jest w kolekcjiA który z nich faktycznie pojawia się w
interface
? Żaden! Nic winterface
tym nie mówi, żeAdd
metoda musi w ogóle dodawać , równie dobrze mogłaby usunąć element z kolekcji.Jest to całkowicie poprawna realizacja tego
interface
:Inny przykład: gdzie w
java.util.Set<E>
rzeczywistości jest napisane, że jest to zestaw ? Nigdzie! A dokładniej w dokumentacji. Po angielsku.Prawie we wszystkich przypadkach
interfaces
, zarówno z języka Java, jak i .NET, wszystkie istotne informacje znajdują się w dokumentacji, a nie w typach. Więc jeśli typy i tak nie mówią ci nic interesującego, po co je w ogóle trzymać? Dlaczego nie trzymać się tylko dokumentacji? I to jest dokładnie to, co robi Ruby.Zwróć uwagę, że istnieją inne języki, w których interfejs można faktycznie opisać w zrozumiały sposób. Jednak te języki zazwyczaj nie wywołują konstrukcji opisującej interfejs "
interface
", nazywają jątype
. W języku programowania z typami zależnymi możesz na przykład wyrazić właściwości, żesort
funkcja zwraca kolekcję o tej samej długości co oryginał, że każdy element, który jest w oryginale, również znajduje się w posortowanej kolekcji i nie ma większego elementu pojawia się przed mniejszym elementem.Krótko mówiąc: Ruby nie ma odpowiednika w Javie
interface
. To nie mają jednak odpowiednikiem Java interfejs i jest dokładnie taka sama jak w Javie: dokumentacja.Podobnie jak w Javie, testy akceptacji mogą być również używane do określania interfejsów .
W szczególności w języku Ruby interfejs obiektu jest określany przez to, co może zrobić , a nie przez to , co
class
jest lub z czymmodule
się miesza. Do każdego obiektu, który ma<<
metodę, można dołączyć. Jest to bardzo przydatne w testach jednostkowych, w których można po prostu przekazać komendęArray
lub aString
zamiast bardziej skomplikowanejLogger
, nawet jeśliArray
iLogger
nie udostępniać jawnego,interface
poza tym, że oba mają metodę nazwaną<<
.Innym przykładem jest to
StringIO
, który realizuje te same interfejsu aIO
, a tym samym duża część interfejsu zFile
, ale nie dzielą każdą oprócz wspólnego przodkaObject
.źródło
interface
jest bezużyteczna, pomijając sens jej użycia. Byłoby łatwiej powiedzieć, że ruby jest wpisywany dynamicznie, co ma inny cel i sprawia, że koncepcje takie jak IOC są niepotrzebne / niepożądane. Trudna zmiana, jeśli jesteś przyzwyczajony do projektowania na podstawie umowy. Coś, na czym Railsy mogą skorzystać, co główny zespół zdał sobie sprawę, jak widać w najnowszych wersjach.interface
może nie zawierać wszystkich istotnych informacji, ale zapewnia oczywiste miejsce na umieszczenie dokumentacji. Napisałem klasę w Rubim, która implementuje (wystarczającą ilość) IO, ale zrobiłem to metodą prób i błędów i nie byłem zbyt zadowolony z tego procesu. Napisałem również wiele własnych implementacji interfejsu, ale udokumentowanie, które metody są wymagane i co mają robić, aby inni członkowie mojego zespołu mogli tworzyć implementacje, okazało się wyzwaniem.interface
Konstrukt jest rzeczywiście potrzebne tylko do leczenia różnych typów jako samo w statycznie typowanych języków pojedynczego dziedziczenia (np treatLinkedHashSet
iArrayList
zarówno jakoCollection
), ma praktycznie nic wspólnego z interfejsem jak owa odpowiedź pokazy. Ruby nie jest wpisywany statycznie, więc nie ma potrzeby konstruowania .Wypróbuj „udostępnione przykłady” rspec:
https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples
Piszesz specyfikację swojego interfejsu, a następnie umieszczasz jedną linię w specyfikacji każdego implementującego, np.
Kompletny przykład:
Aktualizacja : Osiem lat później (2020 r.) Ruby obsługuje teraz statycznie typowane interfejsy przez sorbet. Zobacz klasy abstrakcyjne i interfejsy w dokumentacji sorbet.
źródło
Ruby nie ma takiej funkcjonalności. W zasadzie ich nie potrzebuje, ponieważ Ruby używa tak zwanego pisania kaczego .
Istnieje kilka podejść, które możesz zastosować.
Napisz implementacje, które zgłaszają wyjątki; jeśli podklasa spróbuje użyć niezaimplementowanej metody, zakończy się to niepowodzeniem
Wraz z powyższym powinieneś napisać kod testowy, który wymusza twoje kontrakty (jaki inny post tutaj nieprawidłowo wywołuje interfejs )
Jeśli zauważysz, że piszesz metody void, jak zawsze, napisz moduł pomocniczy, który to wychwyci
Teraz połącz powyższe z modułami Ruby i jesteś blisko tego, czego chcesz ...
A potem możesz to zrobić
Pozwólcie, że jeszcze raz podkreślę: jest to podstawa, ponieważ wszystko w Rubim dzieje się w czasie wykonywania; nie ma sprawdzania czasu kompilacji. Jeśli połączysz to z testowaniem, powinieneś być w stanie wykryć błędy. Co więcej, jeśli pójdziesz dalej, prawdopodobnie będziesz w stanie napisać interfejs, który sprawdza klasę po raz pierwszy, gdy tworzony jest obiekt tej klasy; Twoje testy są tak proste, jak zadzwonienie
MyCollection.new
... tak, przesadzone :)źródło
Jak wszyscy mówili, nie ma systemu interfejsu dla Rubiego. Ale dzięki introspekcji możesz łatwo zaimplementować to samodzielnie. Oto prosty przykład, który można ulepszyć na wiele sposobów, aby ułatwić rozpoczęcie pracy:
Usunięcie jednej z metod zadeklarowanych w Person lub zmiana jej liczby argumentów spowoduje wywołanie
NotImplementedError
.źródło
W Javie nie ma takich rzeczy jak interfejsy. Ale są też inne rzeczy, którymi możesz się cieszyć w rubinie.
Jeśli chcesz zaimplementować jakieś typy i interfejsy - aby obiekty można było sprawdzić, czy mają jakieś metody / komunikaty, których od nich wymagasz - możesz wtedy przyjrzeć się rubycontractom . Definiuje mechanizm podobny do PyProtocols . Blog o sprawdzaniu typów w Rubim jest tutaj .
Wspomniane podejścia nie są żywymi projektami, chociaż początkowo cel wydaje się fajny, wydaje się, że większość programistów Ruby może żyć bez ścisłego sprawdzania typu. Ale elastyczność ruby umożliwia zaimplementowanie sprawdzania typu.
Jeśli chcesz rozszerzyć obiekty lub klasy (to samo w Ruby) o określone zachowania lub w pewnym sensie mieć Ruby sposób wielokrotnego dziedziczenia, użyj mechanizmu
include
lubextend
. Za pomocąinclude
możesz dołączyć do obiektu metody z innej klasy lub modułu. Za pomocąextend
możesz dodać zachowanie do klasy, dzięki czemu jej instancje będą miały dodane metody. To było jednak bardzo krótkie wyjaśnienie.Moim zdaniem najlepszym sposobem rozwiązania problemu interfejsu Java jest zrozumienie modelu obiektowego ruby (patrz na przykład wykłady Dave'a Thomasa ). Zapewne zapomnisz o interfejsach Java. Lub masz wyjątkową aplikację w swoim harmonogramie.
źródło
Jak wskazuje wiele odpowiedzi, w Rubim nie ma sposobu, aby zmusić klasę do implementacji określonej metody poprzez dziedziczenie z klasy, w tym modułu lub czegokolwiek podobnego. Powodem tego jest prawdopodobnie rozpowszechnienie TDD w społeczności Ruby, czyli innego sposobu definiowania interfejsu - testy określają nie tylko sygnatury metod, ale także zachowanie. Dlatego jeśli chcesz zaimplementować inną klasę, która implementuje jakiś już zdefiniowany interfejs, musisz upewnić się, że wszystkie testy przejdą.
Zazwyczaj testy są definiowane w izolacji przy użyciu prób i kodów pośredniczących. Ale są też narzędzia takie jak Bogus , pozwalające na definiowanie testów kontraktowych. Takie testy nie tylko definiują zachowanie klasy „podstawowej”, ale także sprawdzają, czy metody stubbed istnieją w klasach współpracujących.
Jeśli naprawdę interesują cię interfejsy w Rubim, polecam użycie frameworka testowego, który implementuje testowanie kontraktowe.
źródło
Wszystkie przykłady tutaj są interesujące, ale brakuje walidacji kontraktu Interface, mam na myśli, że jeśli chcesz, aby twój obiekt zaimplementował wszystkie definicje metod interfejsu, a tylko te nie możesz. Proponuję więc szybki, prosty przykład (z pewnością można go ulepszyć), aby upewnić się, że masz dokładnie to, czego oczekujesz, dzięki interfejsowi (kontrakt).
rozważ swój interfejs z takimi zdefiniowanymi metodami
Następnie możesz napisać obiekt z przynajmniej kontraktem Interface:
Możesz bezpiecznie wywołać swój obiekt za pośrednictwem interfejsu, aby upewnić się, że jesteś dokładnie tym, co definiuje interfejs
Możesz również upewnić się, że obiekt implementuje wszystkie definicje metod interfejsu
źródło
Rozszerzyłem nieco odpowiedź Carlosayama na moje dodatkowe potrzeby. Dodaje to kilka dodatkowych wymuszeń i opcji do klasy Interface:
required_variable
ioptional_variable
która obsługuje wartość domyślną.Nie jestem pewien, czy chciałbyś użyć tego metaprogramowania z czymś zbyt dużym.
Jak stwierdziły inne odpowiedzi, najlepiej jest pisać testy, które odpowiednio egzekwują to, czego szukasz, zwłaszcza gdy chcesz zacząć wymuszać parametry i zwracać wartości.
Z zastrzeżeniem, że ta metoda zgłasza błąd tylko podczas wywoływania kodu. Testy będą nadal wymagane do prawidłowego egzekwowania prawa przed uruchomieniem.
Przykład kodu
interface.rb
plugin.rb
Użyłem biblioteki singleton dla danego wzorca, którego używam. W ten sposób wszystkie podklasy dziedziczą pojedynczą bibliotekę podczas implementowania tego „interfejsu”.
my_plugin.rb
Dla moich potrzeb wymaga to, aby klasa implementująca "interface" była jej podklasą.
źródło
Sam Ruby nie ma dokładnego odpowiednika interfejsów w Javie.
Ponieważ jednak taki interfejs może być czasami bardzo przydatny, sam opracowałem perełkę dla Rubiego, która emuluje interfejsy Java w bardzo prosty sposób.
To się nazywa
class_interface
.Działa po prostu. Najpierw zainstaluj klejnot przez
gem install class_interface
lub dodaj go do swojego Gemfile i rundbundle install
.Definiowanie interfejsu:
Implementacja tego interfejsu:
Jeśli nie zaimplementujesz określonej stałej lub metody lub numer parametru nie pasuje, odpowiedni błąd zostanie zgłoszony przed wykonaniem programu Ruby. Możesz nawet określić typ stałych, przypisując typ w interfejsie. Jeśli nil, dozwolony jest dowolny typ.
Metoda „implements” musi zostać wywołana w ostatnim wierszu klasy, ponieważ jest to pozycja kodu, w której zaimplementowane powyżej metody są już sprawdzane.
Więcej na: https://github.com/magynhard/class_interface
źródło
Zdałem sobie sprawę, że zbyt często używam wzorca „Nie zaimplementowano błędu” do sprawdzania bezpieczeństwa obiektów, dla których chciałem mieć określone zachowanie. Skończyło się na napisaniu klejnotu, który w zasadzie pozwala na użycie takiego interfejsu:
Nie sprawdza argumentów metod. Działa od wersji0.2.0
. Bardziej szczegółowy przykład na https://github.com/bluegod/rintźródło