Co jest odpowiednikiem interfejsu java w Ruby?
Czy możemy ujawnić interfejsy w Rubim tak jak to robimy w Javie i wymusić Moduły lub klasy Rubiego, aby zaimplementować metody zdefiniowane przez interfejs.
Jednym ze sposobów jest użycie dziedziczenia i method_missing, aby osiągnąć to samo, ale czy jest jakieś inne bardziej odpowiednie podejście dostępne ?
9 answers
Ruby ma Interfejsy tak jak każdy inny język.
Należy pamiętać, że należy uważać, aby nie łączyć pojęcia interfejsu , który jest abstrakcyjną specyfikacją obowiązków, gwarancji i protokołów jednostki z pojęciem interface
, które jest słowem kluczowym w Javie, C# i C#. VB.NET języki programowania. W Rubim używamy pierwszego przez cały czas, ale drugiego po prostu nie ma.
Bardzo ważne jest, aby wyróżnić dwa. Ważny jest interfejs , a nie interface
. interface
nie mówi nic użytecznego. Nic nie pokazuje tego lepiej niż marker interfaces w Javie, które są interfejsami, które w ogóle nie mają członków: wystarczy spojrzeć na java.io.Serializable
oraz java.lang.Cloneable
; te dwa znaczą Bardzo różne rzeczy, ale mają dokładnie ten sam podpis.
Więc jeśli dwa interface
oznaczają różne rzeczy, mają to samo podpis, co dokładnie gwarantuje ci interface
?
Kolejny dobry przykład:
package java.util;
interface List<E> implements Collection<E>, Iterable<E> {
void add(int index, E element)
throws UnsupportedOperationException, ClassCastException,
NullPointerException, IllegalArgumentException,
IndexOutOfBoundsException;
}
Co To jest interfejs z java.util.List<E>.add
?
- że długość zbioru nie zmniejsza się
- że wszystkie przedmioty, które były wcześniej w kolekcji, nadal tam są
- to {[11] } jest w zbiorze
I który z nich faktycznie pojawia się w interface
? Żadnego! W interface
nie ma nic, co by mówiło, że Add
metoda musi nawet dodać w ogóle, może równie dobrze usunąć element z kolekcji.
Jest to doskonale poprawna implementacja tego interface
:
class MyCollection<E> implements java.util.List<E> {
void add(int index, E element)
throws UnsupportedOperationException, ClassCastException,
NullPointerException, IllegalArgumentException,
IndexOutOfBoundsException {
remove(element);
}
}
Inny przykład: gdzie w java.util.Set<E>
czy jest napisane, że jest to, no wiesz, zestaw ? Nigdzie! A dokładniej w dokumentacji. Po Angielsku.
W prawie wszystkich przypadkach interfaces
, zarówno z Javy jak i. NET, wszystkie istotne informacje są właściwie w dokumentach, nie w typach. Więc, jeśli typy i tak nie mówią nic ciekawego, po co je w ogóle trzymać? Dlaczego nie trzymać się tylko dokumentacji? I to właśnie robi Ruby.
Zauważ, że istnieją inne języki, w których interfejs może być właściwie opisany w znaczący sposób. Jednak języki te zazwyczaj nie wywołują konstrukcji opisującej interfejs "interface
", nazywają to type
. W zależności od typu język programowania, można na przykład wyrazić właściwości, że funkcja sort
zwraca kolekcję o tej samej długości co oryginał, że każdy element, który jest w oryginale, jest również w sortowanej kolekcji i że żaden większy element nie pojawia się przed mniejszym elementem.
W skrócie: Ruby nie ma odpowiednika Javy interface
. W przeciwieństwie do Javy, interfejs Java jest podobny do interfejsu Java, który jest identyczny z interfejsem Java.: dokumentacja.
Podobnie jak w Javie, testy akceptacyjne mogą być również używane do określania interfejsów .
W szczególności, w Rubim, interfejs obiektu jest określony przez to, co może zrobić , a nie to, co class
jest, lub w co module
miesza się. Do każdego obiektu posiadającego metodę <<
można dołączyć. Jest to bardzo przydatne w testach jednostkowych, gdzie można po prostu przejść w Array
lub String
zamiast bardziej skomplikowanego Logger
, nawet chociaż Array
i Logger
nie mają jednoznacznego interface
, poza tym, że obie mają metodę o nazwie <<
.
Innym przykładem jest StringIO
, który implementuje ten sam interfejs Jak IO
, a więc dużą część interfejsu z File
, ale bez wspólnego przodka poza Object
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-20 17:34:21
Wypróbuj "wspólne przykłady" rspec:
Https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples
Piszesz spec dla swojego interfejsu, a następnie umieszczasz jedną linię w specyfikacji każdego implementatora, np.
it_behaves_like "my interface"
Kompletny przykład:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-10 16:32:17
Czy możemy wystawiać interfejsy w Rubim, tak jak to robimy w Javie i wymuszać Moduły lub klasy Rubiego, aby zaimplementować metody zdefiniowane przez interfejs.
Ruby nie ma takiej funkcjonalności. W zasadzie nie potrzebuje ich, ponieważ Ruby używa tak zwanego typowania kaczek.
Istnieje kilka podejść, które możesz podjąć.
Pisać implementacje, które wywołują wyjątki; jeśli podklasa spróbuje użyć metody nie zaimplementowanej, będzie fail
class CollectionInterface
def add(something)
raise 'not implemented'
end
end
Wraz z powyższym powinieneś napisać kod testowy, który wymusza Twoje Kontrakty (jaki inny post tutaj nieprawidłowo wywołuje interfejs)
Jeśli znajdziesz się pisząc metody void jak zawsze, to napisz moduł pomocniczy, który przechwytuje to
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
Teraz połącz powyższe z modułami Ruby i jesteś blisko tego, czego chcesz...
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
And then you can do
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
Pozwolę sobie jeszcze raz podkreślić: jest to elementarny, jak 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 weźmiesz powyższe pod uwagę, prawdopodobnie będziesz w stanie napisać interfejs, który sprawdza klasę po raz pierwszy, gdy obiekt tej klasy jest tworzony; co sprawia, że testy są tak proste, jak wywołanie MyCollection.new
... tak, na topie:)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-16 07:09:20
Jak wszyscy tutaj mówili, nie ma żadnego systemu interfejsu dla Rubiego. Ale poprzez introspekcję możesz ją całkiem łatwo wdrożyć. Oto prosty przykład, który można poprawić na wiele sposobów, aby pomóc Ci zacząć:
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
Usunięcie jednej z metod zadeklarowanych przez osobę lub zmiana jej liczby argumentów spowoduje powstanie NotImplementedError
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-16 14:40:35
Nie ma czegoś takiego jak interfejsy w Javie. Ale są inne rzeczy, które możesz cieszyć się w ruby.
Jeśli chcesz zaimplementować jakieś typy i interfejsy-aby obiekty mogły być sprawdzane, czy mają jakieś metody/wiadomości, których od nich wymagasz-możesz rzucić okiem narubycontracts . Definiuje mechanizm podobny do PyProtocols . Blog o sprawdzaniu typu w Rubim jest tutaj .
Wspomniane podejście nie jest żywe projekty, chociaż cel wydaje się być miły na początku, wydaje się, że większość programistów ruby może żyć bez ścisłego sprawdzania typu. Ale elastyczność ruby pozwala zaimplementować sprawdzanie typu.
Jeśli chcesz rozszerzyć obiekty lub klasy (to samo w Rubim) o określone zachowania lub w pewnym sensie mieć Ruby sposób wielokrotnego dziedziczenia, użyj mechanizmu include
LUB extend
. Za pomocą include
możesz dołączyć metody z innej klasy lub modułu do obiektu. Za pomocą extend
możesz dodać zachowanie do klasy, tak aby jej instancje miały dodane metody. To było bardzo krótkie wyjaśnienie.
Moim zdaniem najlepszym sposobem rozwiązania potrzeb interfejsu Java jest zrozumienie modelu obiektowego ruby(zobacz na przykład wykłady Dave ' a Thomasa ). Prawdopodobnie zapomnisz o interfejsach Java. Lub masz wyjątkową aplikację w swoim harmonogramie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-25 08:16:28
Jak wiele odpowiedzi wskazuje, nie ma sposobu, aby zmusić klasę do zaimplementowania 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, co jest innym sposobem definiowania interfejsu - testy określają nie tylko sygnatury metod, ale także zachowanie. Tak więc jeśli chcesz zaimplementować inną klasę, która implementuje jakiś już zdefiniowany interfejs, masz aby upewnić się, że wszystkie testy przechodzą.
Zazwyczaj testy są definiowane w izolacji za pomocą mocks i stubs. Ale są też narzędzia takie jak Bogus , umożliwiające 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 testing, który implementuje testowanie kontraktów.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-15 20:20:19
Wszystkie przykłady tutaj są interesujące, ale brakuje walidacji umowy interfejsu, to znaczy, jeśli chcesz, aby twój obiekt zaimplementował definicję wszystkich metod interfejsu, a tylko tych, których nie możesz. proponuję więc szybki prosty przykład (można poprawić na pewno), aby upewnić się, że masz dokładnie to, czego oczekujesz za pośrednictwem interfejsu (umowy).
Rozważ swój interfejs z takimi zdefiniowanymi metodami
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 [email protected]_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
Wtedy można napisać obiekt z co najmniej Kontrakt interfejsu:
class FooImplementation
def foo
puts('foo')
end
def bar
puts('bar')
end
end
Możesz wywołać obiekt bezpiecznie przez interfejs, aby upewnić się, że jesteś dokładnie tym, co definiuje Interfejs
# > FooInterface.new(FooImplementation.new).foo
# => foo
# > FooInterface.new(FooImplementation.new).bar
# => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition
I równie dobrze możesz zapewnić, że Twój obiekt zaimplementuje wszystkie definicje metod interfejsu
class BadFooImplementation
end
# > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-28 15:04:36
Zdałem sobie sprawę, że używam wzorca "nie zaimplementowany błąd" zbyt dużo do kontroli bezpieczeństwa obiektów, które chciałem określonego zachowania. Skończyło się na napisaniu klejnotu, który w zasadzie pozwala na korzystanie z interfejsu takiego jak ten:
require 'playable'
class Instrument
implements Playable
end
Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable
nie sprawdza argumentów metody . Działa od wersji 0.2.0
.
Bardziej szczegółowy przykład na https://github.com/bluegod/rint
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-07 15:10:43
Rozszerzyłem trochę o odpowiedź carlosayama na moje dodatkowe potrzeby. Dodaje to kilka dodatkowych wymuszeń i opcji do klasy interfejsu: required_variable
i optional_variable
, która obsługuje wartość domyślną.
Nie jestem pewien, czy chciałbyś używać tego meta programowania z czymś zbyt dużym.
Jak stwierdziły inne odpowiedzi, najlepiej napisać testy, które prawidłowo wymuszają to, czego szukasz, zwłaszcza gdy chcesz zacząć wymuszać parametry i zwracać wartości.
Caveat ta metoda powoduje błąd tylko przy wywołaniu kodu. Testy nadal będą wymagane do prawidłowego wykonania przed uruchomieniem.
Przykład Kodu
Interfejs.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
Użyłem biblioteki singleton dla podanego wzoru, z którego korzystam. W ten sposób wszystkie podklasy dziedziczą bibliotekę singleton podczas implementacji tego "interfejsu".
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
Dla moje potrzeby wymaga to, aby Klasa implementująca "interfejs" go podklasowała.
class MyPlugin < Plugin
@name = 'My Plugin'
@description = 'I am a plugin'
@safe = true
def self.run
puts 'Do Stuff™'
end
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 19:13:49