Jak zaimplementować klasę abstrakcyjną w języku Ruby?


Wiem, że w rubinie nie ma koncepcji klasy abstrakcyjnej. Ale jeśli w ogóle trzeba to wdrożyć, to jak to zrobić? Próbowałem czegoś takiego ...
class A
def self.new
raise 'Doh! You are trying to write Java in Ruby!'
end
endclass B < A
...
...
end

Ale kiedy próbuję utworzyć wystąpienie B, wywołuje wewnętrznie
A.new
, co zgłasza wyjątek.
Ponadto nie można tworzyć instancji modułów, ale także nie można ich dziedziczyć. uczynienie nowej metody prywatną również jest niemożliwe. Jakieś wskazówki?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Myślę, że nie ma powodu, aby powstrzymywać kogoś przed utworzeniem instancji klasy abstrakcyjnej,

Zwłaszcza, ponieważ mogą dodać do niej metody w locie
http://asqyzeron.wordpress.com ... time/
.
Języki kaczy, takie jak Ruby, używają obecności/nieobecności lub zachowania metod w czasie wykonywania, aby określić, czy należy je wywołać, czy nie. Więc twoje pytanie odnosi się do abstrakcji

metoda

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

i na tym historia powinna się zakończyć. Jedynym powodem używania klas abstrakcyjnych w Javie jest naleganie, aby niektóre metody zostały wypełnione, podczas gdy inne mają własne zachowanie w klasie abstrakcyjnej. W języku pisania kaczych nacisk kładziony jest na metody, a nie klasy/typy, więc powinieneś przenieść swoje zmartwienia na ten poziom.
W swoim pytaniu zasadniczo próbujesz odtworzyć słowo kluczowe
abstract
z języka Java, które jest zapachem kodu do wykonywania języka Java w języku Ruby.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie lubię używać klas abstrakcyjnych w Rubim (prawie zawsze jest lepszy sposób). Jeśli naprawdę uważasz, że jest to najlepsza metoda w danej sytuacji, możesz użyć następującego fragmentu kodu, aby lepiej określić, które metody są abstrakcyjne:
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
endrequire '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

Po prostu wywołujesz
abstract_methods
z listą metod abstrakcyjnych, a gdy są one wywoływane przez instancję klasy abstrakcyjnej, generowany jest wyjątek
NotImplementedError
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Spróbuj:
class A
def initialize
raise 'Doh! You are trying to instantiate an abstract class!'
end
endclass B < A
def initialize
end
end
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

class A
private_class_method :new
endclass B < A
public_class_method :new
end
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

dla każdego w świecie railsów implementacja modelu ActiveRecord jako klasy abstrakcyjnej jest wykonywana z następującą deklaracją w pliku modelu:
self.abstract_class = true
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

W ciągu ostatnich 6 i pół roku programowania w Rubim nigdy nie miałem

potrzebne

Klasa abstrakcyjna.
Jeśli myślisz, że potrzebujesz klasy abstrakcyjnej, myślisz za dużo w języku, który ich dostarcza/wymaga, a nie w języku Ruby jako takim.
Jak sugerowali inni, mixin jest bardziej odpowiedni dla rzeczy, które muszą być interfejsami (tak jak definiuje je Java), a przemyślenie projektu jest bardziej odpowiednie dla rzeczy, które „potrzebują” abstrakcyjnych klas z innych języków, takich jak C ++.
Aktualizacja 2019: Nie potrzebowałem klas abstrakcyjnych w Rubim przez 16 i pół roku użytkowania. Wszystko, co mówią wszystkie osoby komentujące moją odpowiedź, rozwiązuje się, ucząc się języka Ruby i używając odpowiednich narzędzi, takich jak moduły (które dają nawet ogólne implementacje). W poleceniach, którymi zarządzałem, są ludzie, którzy utworzyli klasy z podstawową implementacją, która kończy się niepowodzeniem (na przykład klasa abstrakcyjna), ale jest to głównie marnowanie kodu, ponieważ
NoMethodError
da dokładnie to samo wynik taki sam jak
AbstractClassError
w produkcji.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Moje 2 ¢: Wybieram prostą, lekką mieszankę 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 endend# Usage:
class AbstractBaseWidget
include Abstract
abstract_methods :widgetify
endclass SpecialWidget < AbstractBaseWidget
endSpecialWidget.new.widgetify # <= raises NotImplementedError

I oczywiście dodanie jeszcze jednego błędu w celu zainicjowania klasy bazowej byłoby w tym przypadku trywialne.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Osobiście zgłaszam NotImplementedError w metodach klas abstrakcyjnych. Ale możesz pozostawić to poza „nową” metodą z powodów, o których wspomniałeś.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli chcesz przejść z klasą, której nie można odinstalować, w swojej metodzie A.new, sprawdź, czy występuje self == A przed wyrzuceniem błędu.
Ale w rzeczywistości moduł jest bardziej podobny do tego, czego tu potrzebujesz - na przykład, niezliczone jest klasę abstrakcyjną w innych językach. Technicznie nie można ich podłączyć, ale obejmuje SOMEMODULE Call osiągnie o tym samym celu. Czy jest jakiś powód, dla którego nie działa dla ciebie?
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jaki cel próbujesz służyć z abstrakcyjną klasą? Prawdopodobnie jest lepszy sposób na zrobienie tego w Ruby, ale nie dałeś żadnych szczegółów.
Mój wskaźnik jest taki: użyj

mixin
http://www.rubycentral.com/pic ... .html
zamiast dziedziczenia.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zrobiłem to w ten sposób, więc nadpisuje nowe w klasie potomnej, aby znaleźć nowe w klasie nieabstrakcyjnej.
Nadal nie widzę żadnych praktycznych korzyści ze stosowania klas abstrakcyjnych w języku 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
endclass AbstractParent
extend Abstract
def initialize
puts 'parent initializer'
end
endclass Child < AbstractParent
def initialize
puts 'child initializer'
super
end
end# AbstractParent.new
puts Child.newclass AbstractChild < AbstractParent
extend Abstract
endclass Child2 < AbstractChildend
puts Child2.new
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Inna odpowiedź:
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

Zależy to od zwykłego #method_missing zgłaszania niezaimplementowanych metod,
ale nie pozwala na implementację klas abstrakcyjnych (nawet jeśli mają metodę inicjalizacji)
class A
include Abstract
end
class B < A
endB.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>

Jak sugerowały inne plakaty, prawdopodobnie powinieneś użyć mieszanki, a nie klasy abstrakcyjnej.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jest też ten mały klejnot
abstract_type
, który pozwala subtelnie deklarować abstrakcyjne klasy i moduły.
Przykład (z pliku README.md):
class Foo
include AbstractType # Declare abstract instance method
abstract_method :bar # Declare abstract singleton method
abstract_singleton_method :baz
endFoo.new # raises NotImplementedError: Foo is an abstract type
Foo.baz # raises NotImplementedError: Foo.baz is not implemented# Subclassing to allow instantiation
class Baz < Foo; endobject = Baz.new
object.bar # raises NotImplementedError: Baz#bar is not implemented
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie ma nic złego w Twoim podejściu. Zgłaszanie błędu podczas inicjalizacji wydaje się być w porządku, o ile wszystkie podklasy zastępują inicjalizację. Ale nie chcesz definiować siebie. Nowy w ten sposób. Oto co bym zrobił.
class A
class AbstractClassInstiationError < RuntimeError; end
def initialize
raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
end
end

Innym podejściem byłoby umieszczenie całej tej funkcjonalności w module, którego, jak wspomniałeś, nigdy nie można uruchomić. Następnie włącz moduł do swoich klas, zamiast dziedziczyć po innej klasie. Jednak to by zepsuło rzeczy takie jak super.
Więc wszystko zależy od tego, jak chcesz to zorganizować. Chociaż moduły wydają się być czystszym rozwiązaniem problemu „jak napisać pewne rzeczy, których raczą używać inne klasy”
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Chociaż nie wygląda jak Ruby, możesz to zrobić:
<pre class="lang-rb prettyprint-override">
class A
def initialize
raise 'abstract class' if self.instance_of?(A) puts 'initialized'
end
endclass B < A
end

Wyniki:
>> A.new
(rib):2:in `main'
(rib):2:in `new'
(rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się