Что такое эквивалент Java-интерфейса в Ruby? - PullRequest
91 голосов
/ 14 декабря 2010

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

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

Ответы [ 10 ]

84 голосов
/ 14 декабря 2010

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

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

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

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

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

Также, как и в Java, Приемочные тесты можно также использовать для указания Интерфейсов .

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

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

45 голосов
/ 22 марта 2012

Попробуйте "общие примеры" 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
35 голосов
/ 16 июля 2014

Можем ли мы предоставлять интерфейсы в 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 происходит во время выполнения;нет проверки времени компиляции.Если вы соедините это с тестированием, вы сможете обнаружить ошибки.Более того, если вы продолжите описанное выше, вы, вероятно, сможете написать Interface , который выполняет проверку класса при первом создании объекта этого класса;сделать ваши тесты такими же простыми, как вызов MyCollection.new ... ага, а не просто:)

9 голосов
/ 16 октября 2013

Как все здесь сказали, для 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. * 1005.*

4 голосов
/ 15 мая 2015

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

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

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

4 голосов
/ 14 декабря 2010

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

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

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

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

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

3 голосов
/ 28 мая 2015

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

рассмотрите ваш интерфейс с определенными методами, подобными этому

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

Затем вы можете написать объект как минимум с Контрактом интерфейса:

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
2 голосов
/ 14 марта 2019

У самого Ruby нет точного эквивалента интерфейсов в Java.

Однако, поскольку такой интерфейс иногда может быть очень полезным, я разработал гем для Ruby, который очень просто имитирует интерфейсы Java.

Это называется class_interface.

Работает довольно просто. Сначала установите драгоценный камень с помощью gem install class_interface или добавьте его в свой Gemfile и запустите bundle install.

Определение интерфейса:

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

Реализация этого интерфейса:

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

Если вы не реализуете определенную константу или метод или номер параметра не совпадает, перед выполнением программы Ruby возникнет соответствующая ошибка. Вы даже можете определить тип констант, назначив тип в интерфейсе. Если ноль, допускается любой тип.

Метод "Implements" должен вызываться в последней строке класса, потому что это позиция кода, в которой реализованные выше методы уже проверены.

Больше на: https://github.com/magynhard/class_interface

2 голосов
/ 27 декабря 2017

Я немного расширил ответ Карлосаяма для моих дополнительных нужд.Это добавляет пару дополнительных принудительных мер и опций к классу Interface: required_variable и optional_variable, которые поддерживают значение по умолчанию.

Я не уверен, что вы захотите использовать это метапрограммирование с чем-то слишком большим.

Как уже говорилось в других ответах, вам лучше всего писать тесты, которые должным образом реализуют то, что вы ищете, особенно если вы хотите начать применять параметры и возвращаемые значения.

Предостережение этот метод генерирует ошибку только при вызове кода.Тесты все еще потребуются для правильного применения до выполнения.

Пример кода

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

Я использовал одноэлементную библиотеку для данного шаблона, который я использую.Таким образом, любые подклассы наследуют одноэлементную библиотеку при реализации этого «интерфейса».

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

Для моих нужд это требует, чтобы класс, реализующий "интерфейс "подклассы это.

class MyPlugin < Plugin

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

  def self.run
    puts 'Do Stuff™'
  end
end
0 голосов
/ 13 ноября 2015

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...