Что такое Java-эквивалент в Ruby?

Можем ли мы разоблачить интерфейсы в Ruby, как в java, и внедрить модули Ruby или классы для реализации методов, определенных интерфейсом.

Один из способов - использовать наследование и метод_missing для достижения того же, но есть ли другой более подходящий подход?

Ответ 1

Ruby имеет интерфейсы, как и любой другой язык.

Обратите внимание, что вы должны быть осторожны, чтобы не сблизить концепцию интерфейса, которая является абстрактной спецификацией обязанностей, гарантий и протоколов блока с концепцией interface, которая является ключевым словом в Java, Языки программирования С# и VB.NET. В Ruby мы все время используем первое, но последнего просто не существует.

Очень важно различать два. Что важнее Интерфейс, а не interface. interface говорит вам почти ничего полезного. Ничто не демонстрирует это лучше, чем интерфейсы маркера в Java, которые являются интерфейсами, в которых нет участников: просто взгляните на java.io.Serializable и java.lang.Cloneable; эти два interface означают совсем другие вещи, но они имеют точно такую ​​же подпись.

Итак, если два interface, которые означают разные вещи, имеют одну и ту же подпись, что именно означает 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 возвращает коллекцию той же длины, что и оригинал, что каждый элемент, который находится в оригинале, также находится в сортированной коллекции, и что перед меньшим элементом не появляется более крупный элемент.

Итак, короче: Ruby не имеет эквивалента Java interface. Однако он имеет эквивалент Java-интерфейса, и он точно такой же, как в Java: documentation.

Кроме того, как и в Java, тесты Acceptance Tests могут также использоваться для указания интерфейсов.

В частности, в Ruby интерфейс объекта определяется тем, что он может делать, а не тем, что class является, или чем он смешивается. module он смешивает. Любой объект, который имеет метод <<, может быть прилагается. Это очень полезно в модульных тестах, где вы можете просто передать Array или String вместо более сложного Logger, хотя Array и Logger не разделяют явный interface из того, что они оба имеют метод под названием <<.

Другим примером является StringIO, который реализует тот же интерфейс, что и IO, и, следовательно, большая часть интерфейса File, но без совместного использования какого-либо общего предка, кроме Object.

Ответ 2

Попробуйте использовать общие примеры 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

Ответ 3

Можем ли мы предоставить интерфейсы в Ruby, как в java, и обеспечить модули Ruby или классы для реализации методов, определенных интерфейсом.

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... да, сверху:)

Ответ 4

Как говорили все, нет никакой системы интерфейса для ruby. Но через интроспекцию вы можете легко реализовать ее самостоятельно. Вот простой пример, который можно улучшить во многих отношениях, чтобы помочь вам начать работу:

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 или изменение его количества, приведет к повышению NotImplementedError.

Ответ 5

В Java нет таких вещей, как интерфейсы. Но есть и другие вещи, которые вы можете наслаждаться в рубине.

Если вы хотите реализовать какие-то типы и интерфейс - чтобы объекты могли быть проверены, есть ли у них какие-то методы/сообщения, которые вам требуются, вы можете взглянуть на rubycontracts. Он определяет механизм, подобный PyProtocols. Блог о проверке типов в рубине здесь.

Указанные подходы - это не живые проекты, хотя сначала кажется, что цель кажется приятной, кажется, что большинство разработчиков рубинов могут жить без строгой проверки типов. Но гибкость рубинов позволяет осуществлять проверку типов.

Если вы хотите расширять объекты или классы (одно и то же в рубине) определенными типами поведения или несколько иметь рубиновый способ множественного наследования, используйте механизм include или extend. С помощью include вы можете включать методы из другого класса или модуля в объект. С помощью extend вы можете добавить поведение в класс, чтобы его экземпляры имели добавленные методы. Это было очень короткое объяснение.

Я считаю, что лучший способ разрешить интерфейс Java - это понять модель объекта ruby ​​(см. например, лекции Dave Thomas). Вероятно, вы забудете о интерфейсах Java. Или у вас есть исключительное приложение в вашем расписании.

Ответ 6

Как видно из многих ответов, в Ruby нет способа заставить класс реализовать конкретный метод, наследуя от класса, включая модуль или что-то подобное. Причиной этого является, вероятно, распространенность TDD в сообществе Ruby, что является другим способом определения интерфейса - тесты не только определяют сигнатуры методов, но и поведение. Таким образом, если вы хотите реализовать другой класс, который реализует некоторый уже определенный интерфейс, вы должны убедиться, что все тесты пройдены.

Обычно тесты определяются изолированно, используя mocks и stubs. Но есть также такие инструменты, как Bogus, что позволяет определять контрактные тесты. Такие тесты не только определяют поведение "первичного" класса, но также проверяют, что в взаимодействующих классах существуют оштукатуренные методы.

Если вы действительно обеспокоены интерфейсами в Ruby, я бы рекомендовал использовать платформу тестирования, которая реализует тестирование контрактов.

Ответ 7

Все примеры здесь интересны, но не хватает проверки контракта Interface, я имею в виду, если вы хотите, чтобы ваш объект реализовал все определения методов интерфейса, и только это вы не можете. Поэтому я предлагаю вам простой простой пример (можно уверенно улучшить), чтобы убедиться, что у вас есть именно то, что вы ожидаете от своего интерфейса (контракт).

рассмотрите свой интерфейс с определенными методами, такими как

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

Затем вы можете написать объект с хотя бы контрактом 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

И вы также можете обеспечить, чтобы ваш объект реализовал все определения вашего интерфейса.

class BadFooImplementation
end

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

Ответ 8

Я понял, что слишком часто использовал шаблон "Не реализована ошибка" для проверки безопасности объектов, которые мне нужны для конкретного поведения. Закончилось писать драгоценный камень, который в основном позволяет использовать такой интерфейс:

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