Имитация абстрактных классов в Ruby (Rails) - PullRequest
2 голосов
/ 07 января 2011

Я хочу смоделировать абстрактный класс в Ruby on Rails. То есть Я хочу вызвать исключение, если кто-то пытается позвонить Abstract.new, но он должен быть в состоянии позвонить Child.new (пока Child < Abstract).

Как это сделать? Перезапись new и initialize не работает.

Ответы [ 4 ]

10 голосов
/ 07 января 2011

В другом комментарии OP упоминает, что целью абстрактного класса является разделение поведения (методов), необходимых его дочерним элементам. В Ruby это часто лучше всего делать с модулем, который используется для «смешивания» методов, где это необходимо. Например, вместо:

class Abstract
  def foo
    puts "foo!"
  end
end

class Concrete
end

Concrete.new.foo # => "foo!"

это:

module Foo
  def foo
    puts "foo!"
  end
end

class Concrete
  include Foo
end

Concrete.new.foo # => "foo!"

Но вот как можно удовлетворить первоначальный запрос:

#!/usr/bin/ruby1.8

class Abstract

  def initialize(*args)
    raise if self.class == Abstract
    super
  end

end

class Concrete < Abstract
end

Concrete.new # OK
Abstract.new # Raises an exception
7 голосов
/ 07 января 2011

Почему вы хотите это сделать?Задача абстрактных / сопряженных классов - взломать строго типизированные языки в динамическую парадигму.Если вам нужно, чтобы ваш класс вписался в сигнатуру, назовите ваши методы в соответствии с исходным классом или создайте фасад и подключите его, не нужно обманывать компилятор, он просто работает.

def my_printer obj
  p obj.name
end

Таким образом, я определил интерфейс как любой объект со свойством name

class person
  attr_accessor :name
  def initialize
   @name = "Person"
  end
end

class Employee
   attr_accessor :name
  def initialize
   @name = "Employee"
   @wage = 23
  end
end

, поэтому ничто не мешает нам вызывать наш метод принтера с любым из этих

my_printer Person.new
my_printer Employee.new

, оба печатают там имена безсцепка: D

1 голос
/ 28 августа 2012

Вам почти всегда нужно делать это, чтобы применять API, когда какой-то сторонний разработчик собирается реализовать заглушку, и вы уверены, что они испортят это.Вы можете использовать определенные префиксные шаблоны в своем родительском классе и модуль, который анализирует процесс создания для достижения этой цели:

module Abstract
  def check
    local = self.methods - Object.methods
    templates = []
    methods = []
    local.each do |l|
      if l =~ /abstract_(.*)/ # <--- Notice we look for abstract_* methods to bind to.
        templates.push $1
      end
      methods.push l.to_s
    end
    if !((templates & methods) == templates)
      raise "Class #{self.class.name} does not implement the required interface #{templates}"
    end
  end
end

class AbstractParent 
  include Abstract
  def initialize
    check
  end
  def abstract_call # <--- One abstract method here
  end
  def normal_call
  end
end

class Child < AbstractParent # <-- Bad child, no implementation
end

class GoodChild < AbstractParent
  def call # <-- Good child, has an implementation
  end
end

Тест:

begin
  AbstractParent.new
  puts "Created AbstractParent"
rescue Exception => e
  puts "Unable to create AbstractParent"
  puts e.message
end

puts

begin
  Child.new
  puts "Created Child"
rescue Exception => e
  puts "Unable to create Child"
  puts e.message
end

puts

begin
  GoodChild.new
  puts "Created GoodChild"
rescue Exception => e
  puts "Unable to create GoodChild"
  puts e.message
end

Результат:

[~] ruby junk.rb 
Unable to create AbstractParent
Class AbstractParent does not implement the required interface ["call"]

Unable to create Child
Class Child does not implement the required interface ["call"]

Created GoodChild
0 голосов
/ 07 января 2011

Если вы хотите это для ведения ИППП, вы можете следовать рекомендациям в этой теме :

class Periodical < ActiveRecord::Base
  private_class_method :new, :allocate
  validates_presence_of :type
end

class Book < Periodical
  public_class_method :new, :allocate
end

class Magazine < Periodical
  public_class_method :new, :allocate
end

Предостережение: я не уверен, что это рабочее решение. Это скрывает new и allocate в базовом классе и повторно включает их в дочерних классах - но это само по себе не препятствует созданию объектов с create!. Добавление проверки на type предотвращает создание базового класса. Я думаю, вы могли бы также скрыть create!, но я не уверен, охватывает ли это все способы, которыми Rails может создать экземпляр объекта модели.

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