Ruby'de java arayüzü eşdeğeri nedir?


106

Java'da yaptığımız gibi Ruby'de arayüzleri açığa çıkarabilir miyiz ve arayüz tarafından tanımlanan yöntemleri uygulamak için Ruby modüllerini veya sınıflarını zorlayabilir miyiz?

Bir yol, aynısını elde etmek için miras ve method_missing kullanmaktır, ancak daha uygun başka bir yaklaşım var mı?



6
Buna neden ihtiyacın olduğunu kendine iki kez sormalısın. Ruby'de sorun olmayan lanet olası şeyleri derlemek için çoğu zaman yeterli arayüz kullanılır.
Arnis Lapsa

1
Bu soru, [ Ruby'de, C #'daki bir arayüzün eşdeğeri nedir? ] ( StackOverflow.Com/q/3505521/#3507460 ).
Jörg W Mittag

2
Buna neden ihtiyacım var? "Versiyonlanabilir" olarak adlandırabileceğiniz bir şeyi uygulamak istiyorum, bu da dokümanları / dosyaları versiyonlanabilir ama neyi kullanarak versiyonlanabilir yapar .... Örneğin, SVN veya CVS gibi mevcut depo yazılımlarını kullanarak versiyonlanabilir hale getirebilirim. Seçtiğim temel mekanizma ne olursa olsun, bazı temel minimum işlevleri sağlamalıdır. Herhangi bir yeni temel depo uygulamasıyla bu çıplak minimum işlevlerin uygulanmasını sağlamak için arabirim gibi bir şey kullanmak istiyorum.
crazycrv

Sandi Metz, POODR kitabında arayüzleri belgelemek için testler kullanıyor. Bu kitabı okumaya gerçekten değer. 2015 yılı itibariyle @ aleksander-pohl cevabının en iyisi olduğunu söyleyebilirim.
Greg Dan

Yanıtlar:


85

Ruby, diğer dillerde olduğu gibi Arayüzlere sahiptir .

Bir birimin sorumluluklarının, garantilerinin ve protokollerinin soyut bir belirtimi olan Arayüz kavramını interface, Java, C # ve VB.NET programlamasında anahtar kelime olan kavramı ile birleştirmemeye dikkat etmeniz gerektiğini unutmayın. Diller. Ruby'de her zaman ilkini kullanırız, ancak ikincisi basitçe mevcut değildir.

İkisini birbirinden ayırmak çok önemli. Asıl önemli olan ise Arayüz değil interface. Bu interfacesize hemen hemen hiçbir işe yaramaz. Bunu hiçbir üyesi olmayan Java arabirimlerinden daha iyi gösteren hiçbir şey yoktur: sadece bir göz atın java.io.Serializableve java.lang.Cloneable; Bu iki interfaces demek çok farklı şeyler, ama onlar var tam aynı imzayı.

Öyleyse, interfacefarklı şeyler ifade eden iki kelime aynı imzaya sahipse , sizi garanti eden tam olarakinterface nedir?

Bir başka güzel örnek:

package java.util;

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

Nedir Arayüz ait java.util.List<E>.add?

  • koleksiyonun uzunluğunun azalmaması
  • daha önce koleksiyonda bulunan tüm öğelerin hala orada olduğunu
  • bu elementkoleksiyonda

Ve bunlardan hangisi gerçekte interface? Yok! İçinde yöntemin hiç eklemesi gerektiğini interfacesöyleyen hiçbir şey yok , aynı zamanda koleksiyondan bir öğeyi kaldırabilir .Add

Bu, bunun tamamen geçerli bir uygulamasıdır interface:

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

Başka bir örnek: java.util.Set<E>gerçekte nerede bir set olduğunu söylüyor ? Hiçbir yerde! Veya daha doğrusu, belgelerde. İngilizcede.

Hemen hemen tüm durumlarda interfaces, hem Java hem de .NET'te, tüm ilgili bilgiler aslında türlerde değil, belgelerde bulunur. Öyleyse, türler zaten size ilginç bir şey söylemiyorsa, neden onları hiç tutmasın? Neden sadece belgelere bağlı kalmıyorsunuz? Ve işte Ruby tam olarak bunu yapar.

Arayüzün gerçekten anlamlı bir şekilde tanımlanabileceği başka diller de olduğunu unutmayın . Ancak, bu diller tipik olarak Arayüzü tanımlayan yapıyı " " olarak adlandırmazlar, buna " " denir . Bağımlı olarak yazılmış bir programlama dilinde, örneğin, bir fonksiyonun orijinalle aynı uzunlukta bir koleksiyon döndürdüğü, orijinaldeki her öğenin de sıralanmış koleksiyonda yer aldığını ve daha büyük bir öğe olmadığını ifade edebilirsiniz. daha küçük bir elemanın önünde görünür.interfacetypesort

Yani kısaca: Ruby'nin bir Java'ya eşdeğeri yoktur interface. Bu vermez , ancak, bir Java eşdeğer olması Arayüzü ve tam olarak Java aynıdır: dokümantasyonu.

Ayrıca Java'da olduğu gibi, Kabul Testleri de Arayüzleri belirlemek için kullanılabilir .

Özellikle, Ruby, Arayüz bir nesnenin o ne göre belirlenir yapmak değil, neyi classolduğunu, ya da ne moduleiçeri karıştırır. Bir sahip olan her nesne <<yöntemi eklenebilir. Bu sadece bir in geçebilir ünite testleri, çok faydalıdır Arrayya da Stringyerine daha karmaşık bir Loggerhalde Arrayve Loggeraçık bir paylaşmayan interfaceikisi de denilen bir yöntem var aslında dışında <<.

Bir başka örnek ise StringIOolan uygular, aynı arabirimi olarak IOve dolayısıyla büyük bir kısmı arabirimi arasında File, ama bunun yanı sıra, herhangi bir ortak atadan paylaşmayan Object.


284
İyi bir okuma yaparken cevabı o kadar yararlı bulmuyorum. Neden interfaceişe yaramaz olduğuna dair bir tez gibi okur , kullanım noktasını kaçırır. Ruby'nin dinamik olarak yazıldığını ve farklı bir odak noktası olduğunu ve IOC gibi kavramları gereksiz / istenmeyen hale getirdiğini söylemek daha kolay olurdu. Sözleşmeye Göre Tasarım yapmaya alışkınsanız, bu zor bir değişimdir. En son sürümlerde görebileceğiniz gibi çekirdek ekibin farkına vardığı bir şey Rails'in yararlanabileceği bir şeydi.
goliatone

12
Takip eden soru: Ruby'de bir arayüzü belgelemenin en iyi yolu nedir? Bir Java anahtar sözcüğü interface, tüm ilgili bilgileri sağlamayabilir, ancak belgeleri koymak için bariz bir yer sağlar. Ruby'de (yeterince) IO uygulayan bir sınıf yazdım, ancak bunu deneme yanılma yoluyla yaptım ve süreçten pek memnun kalmadım. Ayrıca kendi arayüzümün birden çok uygulamasını da yazdım, ancak ekibimin diğer üyelerinin uygulamaları oluşturabilmesi için hangi yöntemlerin gerekli olduğunu ve ne yapmaları gerektiğini belgelemek zor oldu.
Patrick

9
interface Yapı aslında sadece (örneğin tedavi statik olarak yazılan tek miras dilde aynı şekilde farklı tedavi etmek için gerekli olan LinkedHashSetve ArrayListbir şekilde her ikisi Collection), onunla hemen hemen hiçbir şey yok sahiptir Arabirimi Bu cevap gösterdiği gibi. Ruby statik olarak yazılmadığından yapıya gerek yoktur .
Esailija

16
Bunu "bazı arayüzler bir anlam ifade etmiyor, bu yüzden arayüzler kötü. Neden arayüz kullanmak isteyesiniz?" Şeklinde okudum. Bu soruya cevap vermiyor ve açıkçası, arayüzlerin ne işe yaradığını ve yararlarını anlamayan biri gibi görünüyor.
Oddman

13
List arayüzünün geçersizliği hakkındaki argümanınız, "add" adı verilen bir fonksiyonda bir kaldırma işlemi gerçekleştiren bir metoda atıfta bulunmanızdır. Redüktör ad absurdum argümanının klasik bir örneğidir. Özellikle herhangi bir dilde (yakut dahil) beklenenden farklı bir şey yapan bir yöntem yazmak mümkündür. Bu "arayüz" e karşı geçerli bir argüman değil, sadece kötü kod.
Justin Ohms

61

Rspec'in "paylaşılan örneklerini" deneyin:

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

Arayüzünüz için bir spesifikasyon yazarsınız ve ardından her uygulayıcının spesifikasyonuna bir satır koyarsınız, örn.

it_behaves_like "my interface"

Tam örnek:

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

Güncelleme : Sekiz yıl sonra (2020) Ruby artık sorbet aracılığıyla statik olarak yazılan arayüzleri destekliyor. Sorbe belgelerinde Özet Sınıflar ve Arayüzlere bakın .


17
Bunun kabul edilen cevap olması gerektiğine inanıyorum. Bu, çoğu zayıf dilin Java benzeri arabirimler sağlayabilmesinin yoludur. Kabul edilen, Ruby'nin neden arayüzlere sahip olmadığını, nasıl taklit edileceğini açıklar.
SystematicFrank

1
Kabul ediyorum, bu cevap Ruby'ye geçen bir Java geliştiricisi olarak bana yukarıdaki cevaptan çok daha fazla yardımcı oldu.
Cam

Evet, ancak bir arayüzün tüm amacı, aynı yöntem adlarına sahip olmasıdır, ancak somut sınıflar, muhtemelen farklı olan davranışı uygulayacak olanlar olmalıdır. Öyleyse, paylaşılan örnekte neyi test etmem gerekiyor?
Rob Wise

Ruby her şeyi pragmatik yapar. Belgelenmiş ve iyi yazılmış kodlara sahip olmak istiyorsanız, testleri / özellikleri ekleyin ve bu bir tür statik yazım kontrolü olacaktır.
Dmitry Polushkin

42

Java'da yaptığımız gibi Ruby'de arayüzleri açığa çıkarabilir miyiz ve arayüz tarafından tanımlanan yöntemleri uygulamak için Ruby modüllerini veya sınıflarını zorlayabilir miyiz ?

Ruby bu işleve sahip değildir. Prensipte, Ruby ördek yazma denen şeyi kullandığından bunlara ihtiyaç duymaz .

Alabileceğiniz birkaç yaklaşım var.

İstisnaları artıran uygulamalar yazın; bir alt sınıf, uygulanmamış yöntemi kullanmaya çalışırsa başarısız olur

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

Yukarıdakilerle birlikte, sözleşmelerinizi uygulayan test kodu yazmalısınız (buradaki başka bir gönderi yanlış olarak Arayüzü çağırıyor )

Kendinizi her zaman yukarıdaki gibi geçersiz yöntemler yazarken bulursanız, bunu yakalayan bir yardımcı modül yazın.

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

Şimdi yukarıdakileri Ruby modülleri ile birleştirin ve istediğiniz şeye yakınsınız ...

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

Ve sonra yapabilirsin

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

Bir kez daha vurgulayayım: Ruby'de her şey çalışma zamanında gerçekleştiği için bu bir temeldir; derleme zamanı denetimi yoktur. Bunu test ile birleştirirseniz, hataları alabilmeniz gerekir. Daha da ötesi, yukarıdakileri daha da ileri götürürseniz, muhtemelen o sınıfın bir nesnesi ilk yaratıldığında sınıf üzerinde kontrol gerçekleştiren bir Arayüz yazabilirsiniz ; testlerinizi aramak kadar basit yapmak MyCollection.new... evet, üstte :)


Tamam, ancak Koleksiyonunuz = MyCollection, Arayüzde tanımlanmamış bir yöntemi uygularsa, bu mükemmel çalışır, dolayısıyla Nesnenizin yalnızca Arayüz yöntemleri tanımlarına sahip olduğundan emin olamazsınız.
Joel AZEMAR

Bu oldukça harika, teşekkürler. Ördek yazımı iyidir, ancak bazen diğer geliştiricilere bir arayüzün nasıl davranması gerektiğini açıkça bildirmek iyidir.
Mirodinho

10

Buradaki herkesin söylediği gibi Ruby için bir arayüz sistemi yok. Ancak iç gözlem yoluyla, bunu oldukça kolay bir şekilde uygulayabilirsiniz. İşte başlamanıza yardımcı olmak için birçok şekilde geliştirilebilecek basit bir örnek:

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 üzerinde bildirilen yöntemlerden birinin kaldırılması veya değiştirilen argümanların sayısı a NotImplementedError.


5

Java tarzında arayüz diye bir şey yoktur. Ama Ruby'de tadını çıkarabileceğiniz başka şeyler de var.

Bazı türler ve arayüzler uygulamak istiyorsanız - nesnelerin onlardan istediğiniz bazı yöntemlere / mesajlara sahip olup olmadıklarını kontrol edebilmek için -, ardından rubycontracts'a göz atabilirsiniz . PyProtocols'e benzer bir mekanizma tanımlar . Ruby'de tür denetleme hakkında bir blog burada .

Bahsedilen yaklaşılan canlı projeler değil, hedef ilk bakışta güzel görünse de, Ruby geliştiricilerinin çoğu katı tip kontrolü olmadan yaşayabiliyor gibi görünüyor. Ancak Ruby'nin esnekliği, tür kontrolünün uygulanmasını sağlar.

Nesneleri veya sınıfları (yakutta aynı şey) belirli davranışlarla genişletmek istiyorsanız veya bir şekilde birden fazla kalıtımın yakut yoluna sahipseniz, includeveya extendmekanizmasını kullanın . İle includebaşka bir sınıftan veya modülden yöntemleri bir nesneye dahil edebilirsiniz. İle extendbir sınıfa davranış ekleyebilirsiniz, böylece örneklerinde eklenmiş yöntemler olur. Yine de bu çok kısa bir açıklamaydı.

Bence Java arayüzü ihtiyacını çözmenin en iyi yolu Ruby nesne modelini anlamaktır ( örneğin Dave Thomas derslerine bakın). Muhtemelen Java arayüzlerini unutacaksınız. Veya programınızda olağanüstü bir uygulamanız var.


Şu Dave Thomas dersleri bir ödeme duvarının arkasında.
Purplejacket

5

Pek çok yanıtın da gösterdiği gibi, Ruby'de bir modül veya benzeri bir şey dahil olmak üzere bir sınıftan miras alarak belirli bir yöntemi uygulamaya zorlamanın bir yolu yoktur . Bunun nedeni muhtemelen, arayüzü tanımlamanın farklı bir yolu olan Ruby topluluğundaki TDD'nin yaygınlığıdır - testler yalnızca yöntemlerin imzalarını değil, aynı zamanda davranışı da belirtir. Bu nedenle, önceden tanımlanmış bir arabirimi uygulayan farklı bir sınıf uygulamak istiyorsanız, tüm testlerin başarılı olduğundan emin olmalısınız.

Genellikle testler, örnekler ve taslaklar kullanılarak ayrı ayrı tanımlanır. Ancak , sözleşme testlerini tanımlamaya izin veren Bogus gibi araçlar da vardır . Bu tür testler sadece "birincil" sınıfın davranışını tanımlamakla kalmaz, aynı zamanda stubed yöntemlerin birlikte çalışan sınıflarda var olup olmadığını da kontrol eder.

Ruby'deki arayüzlerle gerçekten ilgileniyorsanız, sözleşme testini uygulayan bir test çerçevesi kullanmanızı tavsiye ederim.


3

Buradaki tüm örnekler ilginçtir ancak Arayüz sözleşmesinin onaylanmasını kaçırır, yani nesnenizin tüm Arayüz yöntemleri tanımını uygulamasını istiyorsanız ve yalnızca bunları yapamazsınız. Bu yüzden, Arayüzünüz (Sözleşme) aracılığıyla tam olarak sahip olmayı beklediğiniz şeye sahip olduğunuzdan emin olmak için size hızlı ve basit bir örnek (kesinlikle geliştirilebilir) öneriyorum.

Arayüzünüzü bunun gibi tanımlanmış yöntemlerle düşünün

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

Daha sonra en azından Arayüz sözleşmesine sahip bir nesne yazabilirsiniz:

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

Arayüzün tam olarak tanımladığı şey olduğunuzdan emin olmak için Arayüzünüz aracılığıyla Nesnenizi güvenle arayabilirsiniz.

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

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

Ve ayrıca Object'in tüm Arayüz yöntemleri tanımınızı uyguladığından emin olabilirsiniz.

class BadFooImplementation
end

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

2

Ek ihtiyaçlarım için carlosayam'ın cevabını biraz genişlettim. Bu, Interface sınıfına birkaç ek zorlama ve seçenek ekler: required_variableve optional_variablebu, varsayılan bir değeri destekler.

Bu meta programlamayı çok büyük bir şeyle kullanmak isteyeceğinizden emin değilim.

Diğer yanıtların da belirttiği gibi, aradığınızı doğru bir şekilde uygulayan testler yazmaktan en iyisisiniz, özellikle de parametreleri ve dönüş değerlerini uygulamaya başlamak istediğinizde.

Bu yöntemin uyarılması, yalnızca kodun çağrılması üzerine bir hata verir. Çalışma zamanından önce uygun uygulama için yine de testler gerekli olacaktır.

Kod Örneği

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

Kullandığım verilen kalıp için singleton kitaplığını kullandım. Bu şekilde, herhangi bir alt sınıf, bu "arabirimi" uygularken tekli kitaplığı miras alır.

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

İhtiyaçlarım için bu, "arabirimi" uygulayan sınıfın onu alt sınıflara ayırmasını gerektirir.

class MyPlugin < Plugin

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

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

2

Ruby'nin kendisinin Java'daki arayüzlerle tam bir eşdeğeri yoktur.

Bununla birlikte, böyle bir arayüz bazen çok faydalı olabileceğinden, Java arayüzlerini çok basit bir şekilde taklit eden Ruby için kendim için bir mücevher geliştirdim.

Denir class_interface.

Oldukça basit çalışıyor. Önce mücevheri yükleyin gem install class_interfaceveya Gemfile ve rund'unuza ekleyin bundle install.

Bir arayüz tanımlama:

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

Bu arayüzü uygulamak:

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

Belirli bir sabit veya yöntemi uygulamazsanız veya parametre numarası eşleşmezse, Ruby programı çalıştırılmadan önce buna karşılık gelen bir hata ortaya çıkar. Arayüzde bir tip atayarak sabitlerin tipini bile belirleyebilirsiniz. Sıfırsa, herhangi bir türe izin verilir.

Yukarıdaki uygulanan yöntemlerin zaten kontrol edildiği kod konumu olduğundan, "uygular" yöntemi bir sınıfın son satırında çağrılmalıdır.

Daha fazlası: https://github.com/magynhard/class_interface


0

Belirli bir davranış istediğim nesneler üzerinde güvenlik kontrolleri için "Uygulanmadı hatası" modelini çok fazla kullandığımı fark ettim. Temelde böyle bir arayüzün kullanılmasına izin veren bir mücevher yazmakla sona erdi:

require 'playable' 

class Instrument 
  implements Playable
end

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

Yöntem argümanlarını kontrol etmez . Versiyon itibariyle yapar 0.2.0. Https://github.com/bluegod/rint adresinde daha ayrıntılı örnek

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.