Je sais qu'il n'y a pas de concept de classe abstraite en 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 appelle en interne A.new
qui va déclencher l'exception.
De plus, les modules ne peuvent pas être instanciés, mais ils ne peuvent pas non plus être hérités. rendre la nouvelle méthode privée ne fonctionnera pas non plus. Des pointeurs?
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 complet. 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
En gros, vous appelez simplement abstract_methods
avec la liste des méthodes abstraites et lorsqu'elles sont appelées par une instance de la classe abstraite, une exception NotImplementedError
est déclenchée.
Juste pour revenir tard ici, je pense qu’il n’ya 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 frappe, comme Ruby, utilisent la présence/absence ou le comportement des méthodes au moment de 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 un sens.
def get_db_name
raise 'this method should be overriden and return the db name'
end
et cela devrait être à la fin de l'histoire. La seule raison d'utiliser des classes abstraites dans Java est d'insister sur le fait que certaines méthodes sont "renseignées", tandis que d'autres ont leur comportement dans la classe abstraite. Dans un langage de saisie de code, l'accent est mis sur sur les méthodes, pas sur les classes/types, vous devriez donc déplacer vos inquiétudes à ce niveau.
Dans votre question, vous essayez essentiellement de recréer le mot-clé abstract
de Java, qui est une odeur de code permettant de faire Java en Ruby.
Essaye ça:
class A
def initialize
raise 'Doh! You are trying to instantiate an abstract class!'
end
end
class B < A
def initialize
end
end
class A
private_class_method :new
end
class B < A
public_class_method :new
end
Mon 2 ¢: J'opte pour un mix 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 lors de l'initialisation de la classe de base serait trivial dans ce cas.
pour quiconque dans le monde Rails, l'implémentation d'un modèle ActiveRecord en tant que classe abstraite est effectuée avec cette déclaration dans le fichier de modèle:
self.abstract_class = true
Au cours des six dernières années et demie de programmation de Ruby, je n'ai pas utilisé une classe abstraite une fois.
Si vous pensez avoir besoin d'une classe abstraite, vous pensez trop dans un langage qui les fournit/en a besoin, pas dans Ruby en tant que tel.
Comme d'autres l'ont suggéré, un mixin est plus approprié pour les éléments supposés être des interfaces (comme le définit Java), et il est plus approprié de repenser votre conception pour les éléments qui "nécessitent" des classes abstraites telles que C++.
Mise à jour 2019: Je n’ai pas eu besoin de classes abstraites en Ruby en 16 ans et demi d’utilisation. Tout ce que tous les commentaires de ma réponse disent est traité en apprenant réellement Ruby et en utilisant les outils appropriés, tels que les modules (qui vous donnent même des implémentations courantes). Il y a des personnes dans des équipes gérées par mes soins qui ont créé des classes dont l'implémentation de base échoue (comme une classe abstraite), mais il s'agit généralement d'un gaspillage de code car NoMethodError
produirait exactement le même résultat qu'un AbstractClassError
en production.
Vous pouvez essayer 3 rubygems:
interface
résumé
résumé simple
Si vous voulez aller avec une classe indéfinissable, dans votre méthode A.new, vérifiez si self == A avant de lancer l'erreur.
Mais en réalité, un module ressemble davantage à ce que vous voulez ici - par exemple, Enumerable est le genre de chose qui pourrait être une classe abstraite dans d’autres langues. Vous ne pouvez techniquement pas les classer, mais vous appelez include SomeModule
atteint à peu près le même objectif. Y a-t-il une raison pour que cela ne fonctionne pas pour vous?
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 la suivante. utilisez un mixin pas d'héritage.
Il y a aussi ce petit abstract_type
gem, permettant de déclarer des classes et des modules abstraits de manière discrète.
Exemple (à partir du fichier README.md ):
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
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 la #method_missing normale 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 d'initialisation)
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'un cours abstrait.
Je l’ai fait de cette façon, ainsi il redéfinit la nouvelle classe enfant pour trouver une nouvelle classe non abstraite. Je ne vois toujours pas d'utilisation pratique des classes abstraites en 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
Personnellement, je soulève NotImplementedError dans les méthodes de classes abstraites. Mais vous voudrez peut-être laisser de côté la "nouvelle" méthode, pour les raisons que vous avez mentionnées.
Gemme 2 lignes: https://rubygems.org/gems/abstract
Rien de mal avec votre approche. Relever une erreur lors de l'initialisation semble bien, tant que toutes vos sous-classes annulent l'initialisation, bien sûr. Mais vous ne voulez pas définir vous-même.nouveau 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 instancié. Ensuite, incluez le module dans vos classes plutôt que d'hériter d'une autre classe. Cependant, cela casserait des choses comme super.
Donc, 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 être utilisées par d'autres classes"