How to limit a type to the class of a interface and not the instance of that interface?

193 Views Asked by At

Given the following Interface in a module:

module Action
  abstract def perform
end

I would like to use it to instantiate different classes that implement it:

class Run
  include Action

  def perform
    puts "run!"
  end
end

class Jump
  include Action

  def perform
    puts "jump!"
  end
end

I know is possible to define an array like [] of Action and be able to store instances of Action, but I'm interested on the classes instead of the instances.

I would like to know how to define the type restriction so I can store a reference to the class that implements the interface and not an specific instance.

My objective is be able to instantiate a fresh instance of certain class and be able to invoke the perform method in it.

At this time it is possible to write the following code:

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

And things will work, however it will not be possible to store that list of classes into a instance variable since the type restrictions are a bit more strict.

What would be the type restriction syntax for that case?

2

There are 2 best solutions below

2
On BEST ANSWER

The first idea that comes to mind is using [] of Action.class, but that doesn't work. Maybe that should work, but it will need a change/enhancement in the compiler.

In the meantime you can do this:

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
0
On

An alternative approach to the Factory suggestion made by @asterite is the usage of a module and extend it on the class but also use it to define the type of the instance variables:

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

This allowed me to also use Action.class as type requirement so only classes that implement Action can be used and using Action::Interface removed the need to perform any casting on the element obtained from the array:

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