Как реализовать абстрактный класс в ruby? - PullRequest
109 голосов
/ 04 февраля 2009

Я знаю, что в ruby ​​нет понятия абстрактного класса. Но если это вообще нужно реализовать, как это сделать? Я пробовал что-то вроде ...

class A
  def self.new
    raise 'Doh! You are trying to write Java in Ruby!'
  end
end

class B < A
  ...
  ...
end

Но когда я пытаюсь создать экземпляр B, он внутренне собирается вызвать A.new, что вызовет исключение.

Кроме того, модули не могут быть созданы, но они также не могут наследоваться. создание нового метода private также не будет работать. Есть указатели?

Ответы [ 16 ]

110 голосов
/ 23 марта 2010

Просто, чтобы перезвонить здесь в конце, я думаю, что нет причин мешать кому-то создавать экземпляр абстрактного класса, особенно потому, что они могут добавлять методы к нему на лету .

Языки типизации утилит, такие как Ruby, используют наличие / отсутствие или поведение методов во время выполнения, чтобы определить, должны ли они вызываться или нет. Поэтому ваш вопрос, поскольку он относится к абстрактному методу , имеет смысл

def get_db_name
   raise 'this method should be overriden and return the db name'
end

и это должно быть конец истории. Единственная причина использовать абстрактные классы в Java - настаивать на том, чтобы некоторые методы были «заполнены», в то время как другие ведут себя в абстрактном классе. В языке утиной типографии основное внимание уделяется методам, а не классам / типам, поэтому вам следует перенести свои заботы на этот уровень.

В вашем вопросе вы пытаетесь воссоздать ключевое слово abstract из Java, которое является запахом кода для работы с Java в Ruby.

56 голосов
/ 04 февраля 2009

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

module Abstract
  def abstract_methods(*args)
    args.each do |name|
      class_eval(<<-END, __FILE__, __LINE__)
        def #{name}(*args)
          raise NotImplementedError.new("You must implement #{name}.")
        end
      END
      # important that this END is capitalized, since it marks the end of <<-END
    end
  end
end

require 'rubygems'
require 'rspec'

describe "abstract methods" do
  before(:each) do
    @klass = Class.new do
      extend Abstract

      abstract_methods :foo, :bar
    end
  end

  it "raises NoMethodError" do
    proc {
      @klass.new.foo
    }.should raise_error(NoMethodError)
  end

  it "can be overridden" do
    subclass = Class.new(@klass) do
      def foo
        :overridden
      end
    end

    subclass.new.foo.should == :overridden
  end
end

По сути, вы просто вызываете abstract_methods со списком абстрактных методов, и когда они вызываются экземпляром абстрактного класса, возникает исключение NotImplementedError.

41 голосов
/ 04 февраля 2009

Попробуйте это:

class A
  def initialize
    raise 'Doh! You are trying to instantiate an abstract class!'
  end
end

class B < A
  def initialize
  end
end
14 голосов
/ 06 февраля 2009
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
12 голосов
/ 10 мая 2012

My 2 ¢: я выбираю простой, легкий DSL миксин:

module Abstract
  extend ActiveSupport::Concern

  included do

    # Interface for declaratively indicating that one or more methods are to be
    # treated as abstract methods, only to be implemented in child classes.
    #
    # Arguments:
    # - methods (Symbol or Array) list of method names to be treated as
    #   abstract base methods
    #
    def self.abstract_methods(*methods)
      methods.each do |method_name|

        define_method method_name do
          raise NotImplementedError, 'This is an abstract base method. Implement in your subclass.'
        end

      end
    end

  end

end

# Usage:
class AbstractBaseWidget
  include Abstract
  abstract_methods :widgetify
end

class SpecialWidget < AbstractBaseWidget
end

SpecialWidget.new.widgetify # <= raises NotImplementedError

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

10 голосов
/ 26 мая 2015

для любого в мире рельсов, реализация модели ActiveRecord в качестве абстрактного класса выполняется с помощью этого объявления в файле модели:

self.abstract_class = true
9 голосов
/ 04 февраля 2009

За последние 6 с половиной лет программирования на Ruby мне не нужно нужен абстрактный класс один раз.

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

Как и предполагали другие, миксин больше подходит для вещей, которые должны быть интерфейсами (как их определяет Java), а переосмысление вашего дизайна больше подходит для вещей, которые "нуждаются" в абстрактных классах из других языков, таких как C ++.

Обновление 2019: мне не нужны абстрактные классы в Ruby за 16 с половиной лет использования. Все, что говорят все, кто комментирует мой ответ, решается путем изучения Ruby и использования соответствующих инструментов, таких как модули (которые даже дают вам общие реализации). В командах, которыми я управлял, есть люди, которые создали классы, у которых базовая реализация не работает (например, абстрактный класс), но в основном это пустая трата кода, потому что NoMethodError даст тот же результат, что и AbstractClassError в рабочей среде. .

6 голосов
/ 12 августа 2011

Вы можете попробовать 3 рубина:
интерфейс
аннотация
простой реферат

4 голосов
/ 04 февраля 2009

Какую цель вы пытаетесь выполнять с абстрактным классом? Вероятно, есть лучший способ сделать это в ruby, но вы не дали никаких подробностей.

Мой указатель таков; используйте mixin не наследование.

4 голосов
/ 04 февраля 2009

Если вы хотите использовать непостижимый класс, в вашем методе A.new проверьте, если self == A, прежде чем выдать ошибку.

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

...