Как ограничить тип классом интерфейса, а не экземпляром этого интерфейса?

Учитывая следующий интерфейс в модуле:

module Action
  abstract def perform
end

Я хотел бы использовать его для создания экземпляров различных классов, которые его реализуют:

class Run
  include Action

  def perform
    puts "run!"
  end
end

class Jump
  include Action

  def perform
    puts "jump!"
  end
end

Я знаю, что можно определить массив типа [] of Action и иметь возможность хранить экземпляры Action, но меня интересуют классы, а не экземпляры.

Я хотел бы знать, как определить ограничение типа, чтобы я мог сохранить ссылку на класс, реализующий интерфейс, а не на конкретный экземпляр.

Моя цель - создать новый экземпляр определенного класса и вызвать в нем метод perform.

На данный момент можно написать следующий код:

actions = [Run, Jump]
actions.each do |klass|
  instance = klass.new.as(Action)
  instance.perform
end

И все будет работать, однако будет невозможно сохранить этот список классов в переменной экземпляра, поскольку ограничения типов немного более строгие.

Каким будет синтаксис ограничения типа в этом случае?


person Luis Lavena    schedule 04.01.2017    source источник
comment
В чем причина создавать новый экземпляр каждый раз вместо того, чтобы использовать тот же самый?   -  person asterite    schedule 07.01.2017
comment
@asterite разработчик может изменять переменные экземпляра при выполнении. Вызов этого по нескольким волокнам / потокам может привести к проблемам. AFAIK свежий экземпляр - лучший сценарий.   -  person Luis Lavena    schedule 10.01.2017


Ответы (2)


Первая идея, которая приходит в голову, - использовать [] of Action.class, но это не работает. Возможно, это должно сработать, но для этого потребуется изменение / улучшение в компиляторе.

А пока вы можете сделать это:

module Action
  abstract def perform
end

class Run
  include Action

  def perform
    puts "run!"
  end
end

class Jump
  include Action

  def perform
    puts "jump!"
  end
end

module ActionFactory
  abstract def new : Action
end

struct GenericActionFactory(T)
  include ActionFactory

  def new
    T.new
  end
end

ary = [] of ActionFactory
ary << GenericActionFactory(Run).new
ary << GenericActionFactory(Jump).new

action = ary[0].new
p action

action = ary[1].new
p action
person asterite    schedule 04.01.2017
comment
Разве в этом случае компилятор не будет выводить T как Jump | Run? Разве это не было бы немного тяжеловато, когда действий может быть порядка 100+? - person Luis Lavena; 05.01.2017
comment
Компилятор внутренне представляет все реализации модуля как объединение, поэтому он будет таким же. По крайней мере, сейчас я не думаю, что это будет иметь значение. В конечном итоге мы можем это улучшить. - person asterite; 06.01.2017

Альтернативный подход к предложению Factory, сделанный @asterite, - это использование модуля и extend его в классе, но также использование его для определения типа переменных экземпляра:

module Action
  module Interface
  end

  macro included
    extend Interface
  end

  abstract def perform
end

class Run
  include Action

  def perform
    puts "run!"
  end
end

class Jump
  include Action

  def perform
    puts "jump!"
  end
end

list = [] of Action::Interface
list << Run
list << Jump

list.each do |klass|
  instance = klass.new
  instance.perform
end

Это позволило мне также использовать Action.class в качестве требования типа, поэтому можно использовать только классы, реализующие Action, а использование Action::Interface устранило необходимость выполнять любое приведение к элементу, полученному из массива:

class Worker
  @actions = [] of Action::Interface

  def add(action : Action.class)
    @actions << action
  end

  def perform
    @actions.each do |klass|
      instance = klass.new
      instance.perform
    end
  end
end

a = Worker.new
a.add Run
a.add Jump
a.add Jump
a.perform
person Luis Lavena    schedule 05.02.2017