Comment implémenter une classe abstraite dans ruby?

Je sais qu'il n'y a pas de concept de classe abstraite dans ruby. Mais si cela doit être mis en œuvre, comment s'y prendre? J'ai essayé quelque chose comme...

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

class B < A
  ...
  ...
end

Mais quand j'essaie d'instancier B, il va en interne appeler A.new qui va déclencher l'exception.

En outre, les modules ne peuvent pas être instanciés mais ils ne peuvent pas être hérités aussi. faire de la nouvelle méthode privée ne fonctionnera pas. Les pointeurs?

101
demandé sur andrewdotn 2009-02-04 20:35:43

16 réponses

Je n'aime pas utiliser des classes abstraites dans Ruby (il y a presque toujours un meilleur moyen). Si vous pensez vraiment que c'est la meilleure technique pour la situation, vous pouvez utiliser l'extrait suivant pour être plus déclaratif sur les méthodes abstraites:

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

Fondamentalement, vous appelez simplement abstract_methods avec la liste des méthodes abstraites, et quand elles sont appelées par une instance de la classe abstraite, une exception NotImplementedError sera déclenchée.

54
répondu nakajima 2017-12-02 15:35:32

Juste pour arriver tard ici, je pense qu'il n'y a aucune raison d'empêcher quelqu'un d'instancier la classe abstraite, surtout parce qu'ils peuvent y ajouter des méthodes à la volée.

Les langages de typage Duck, comme Ruby, utilisent la présence / absence ou le comportement des méthodes à l'exécution pour déterminer si elles doivent être appelées ou non. Par conséquent, votre question, telle qu'elle s'applique à une méthode abstraite , a du sens

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

Et ça devrait être la fin de l'histoire. Le la seule raison d'utiliser des classes abstraites en Java est d'insister pour que certaines méthodes soient "remplies" tandis que d'autres ont leur comportement dans la classe abstraite. Dans un langage de type canard, l'accent est mis sur les méthodes, pas sur les classes/types, donc vous devriez déplacer vos soucis à ce niveau.

Dans votre question, vous essayez essentiellement de recréer le mot-clé abstract de Java, qui est une odeur de code pour faire Java dans Ruby.

103
répondu Dan Rosenstark 2010-03-24 13:02:06

Essayez ceci:

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

class B < A
  def initialize
  end
end
37
répondu Andrew Peters 2009-02-04 18:06:50
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
11
répondu bluehavana 2009-02-06 02:11:32

Mon 2¢: j'opte pour un mixin DSL simple et léger:

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

Et, bien sûr, ajouter une autre erreur pour initialiser la classe de base serait trivial dans ce cas.

11
répondu Anthony Navarre 2012-05-10 06:15:35

Au cours des 6 1/2 dernières années de programmation de Ruby, je n'ai pas eu besoin de une classe abstraite une fois.

Si vous pensez avoir besoin d'une classe abstraite, vous pensez trop dans un langage qui les fournit/les nécessite, pas dans Ruby en tant que tel.

Comme d'autres l'ont suggéré, un mixin est plus approprié pour les choses qui sont supposées être des interfaces (comme Java Les définit), et repenser votre conception est plus approprié pour les choses qui "ont besoin" de classes abstraites d'autres langages comme C++.

10
répondu Austin Ziegler 2009-02-04 20:27:44

Pour n'importe qui dans le monde rails, l'implémentation D'un modèle ActiveRecord en tant que classe abstraite se fait avec cette déclaration dans le fichier de modèle:

self.abstract_class = true
7
répondu Fred Willmore 2015-05-26 18:19:36

Vous pouvez essayer 3 rubygems:
interface
résumé
abstrait simple

5
répondu Nicklasos 2012-08-16 22:04:34

Si vous voulez aller avec une classe uninstantiable, dans votre A. nouvelle méthode, vérifiez si self = = A avant de lancer l'erreur.

Mais en réalité, un module ressemble plus à ce que vous voulez ici-par exemple, Enumerable est le genre de chose qui pourrait être une classe abstraite dans d'autres langages. Techniquement, vous ne pouvez pas les sous-classer, mais appeler include SomeModule atteint à peu près le même objectif. Y a-t-il une raison pour laquelle cela ne fonctionnera pas pour vous?

4
répondu Chuck 2009-02-04 19:08:03

Quel but essayez-vous de servir avec une classe abstraite? Il y a probablement une meilleure façon de le faire en ruby, mais vous n'avez donné aucun détail.

Mon pointeur est le suivant; utilisez un mixin pas d'héritage.

4
répondu jshen 2009-02-04 20:19:45

Il y a aussi cette petite gemme abstract_type, permettant de déclarer des classes et des modules abstraits de manière discrète.

Exemple (à partir du README.md fichier):

class Foo
  include AbstractType

  # Declare abstract instance method
  abstract_method :bar

  # Declare abstract singleton method
  abstract_singleton_method :baz
end

Foo.new  # raises NotImplementedError: Foo is an abstract type
Foo.baz  # raises NotImplementedError: Foo.baz is not implemented

# Subclassing to allow instantiation
class Baz < Foo; end

object = Baz.new
object.bar  # raises NotImplementedError: Baz#bar is not implemented
4
répondu シリル 2014-09-08 05:15:12

Personnellement, je soulève NotImplementedError dans les méthodes de classes abstraites. Mais vous voudrez peut-être laisser sortir de la "nouvelle " méthode", pour les raisons que vous avez mentionnées.

3
répondu Zack 2009-02-04 17:39:40

Une Autre réponse:

module Abstract
  def self.append_features(klass)
    # access an object's copy of its class's methods & such
    metaclass = lambda { |obj| class << obj; self ; end }

    metaclass[klass].instance_eval do
      old_new = instance_method(:new)
      undef_method :new

      define_method(:inherited) do |subklass|
        metaclass[subklass].instance_eval do
          define_method(:new, old_new)
        end
      end
    end
  end
end

Cela repose sur le #method_missing normal pour signaler les méthodes non implémentées, mais empêche les classes abstraites d'être implémentées (même si elles ont une méthode initialize)

class A
  include Abstract
end
class B < A
end

B.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>

Comme les autres affiches l'ont dit, vous devriez probablement utiliser un mixin plutôt qu'une classe abstraite.

3
répondu rampion 2009-02-04 20:23:05

Je l'ai fait de cette façon, donc il redéfinit nouveau sur la classe enfant pour trouver un nouveau sur la classe non abstraite. Je ne vois toujours pas de pratique à utiliser des classes abstraites dans ruby.

puts 'test inheritance'
module Abstract
  def new
    throw 'abstract!'
  end
  def inherited(child)
    @abstract = true
    puts 'inherited'
    non_abstract_parent = self.superclass;
    while non_abstract_parent.instance_eval {@abstract}
      non_abstract_parent = non_abstract_parent.superclass
    end
    puts "Non abstract superclass is #{non_abstract_parent}"
    (class << child;self;end).instance_eval do
      define_method :new, non_abstract_parent.method('new')
      # # Or this can be done in this style:
      # define_method :new do |*args,&block|
        # non_abstract_parent.method('new').unbind.bind(self).call(*args,&block)
      # end
    end
  end
end

class AbstractParent
  extend Abstract
  def initialize
    puts 'parent initializer'
  end
end

class Child < AbstractParent
  def initialize
    puts 'child initializer'
    super
  end
end

# AbstractParent.new
puts Child.new

class AbstractChild < AbstractParent
  extend Abstract
end

class Child2 < AbstractChild

end
puts Child2.new
3
répondu ZeusTheTrueGod 2010-10-29 09:30:32

Rien de mal avec votre approche. Soulever une erreur dans initialize semble bien, tant que toutes vos sous-classes remplacent initialize bien sûr. Mais vous ne voulez pas définir soi-même.des nouvelles comme ça. Voici ce que je ferais.

class A
  class AbstractClassInstiationError < RuntimeError; end
  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
  end
end

Une autre approche serait de mettre toutes ces fonctionnalités dans un module, qui, comme vous l'avez mentionné, ne peut jamais être instiated. Incluez ensuite le module dans vos classes plutôt que d'hériter d'une autre classe. Cependant, cela casserait des choses comme super.

Donc il cela dépend de la façon dont vous voulez le structurer. Bien que les modules semblent être une solution plus propre pour résoudre le problème de "Comment puis-je écrire des choses qui sont daignées pour d'autres classes à utiliser"

1
répondu Alex Wayne 2009-02-04 17:48:33
1
répondu Nikos D 2011-07-05 12:01:11