如何避免Ruby中的抽象类模式?

时间:2014-09-19 13:33:52

标签: ruby

抽象类在Ruby代码中看起来像来自远程行星Java的外星人。我试图收集可以替代这种不需要的模式的Ruby技巧。

让我们采取一个完全随机的例子:

class AbstractRace < Struct.new(:runner_count)
  def go!
    runner_count.times.map do |index|
      Thread.new do
        run(index)
      end
    end.each(&:join)
  end

  def run(index)
    raise 'To be implemented in derivative classes'
  end
end

class RunnerRace < AbstractRace
  def run(index)
    puts "I am runner number #{index}. I am running"
  end
end

class CarRace < AbstractRace
  def run(index)
    puts "I am car number #{index}. I am accelerating"
  end
end

RunnerRace.new(2).go!
CarRace.new(2).go!

如何改写它?一种可能的方法是使用mixin,如下所示:

require 'active_support/concern'

  module Race
    extend ActiveSupport::Concern

    def go!
      participant_count.times.map do |index|
        Thread.new do
          run(index)
        end
      end.each(&:join)
    end

    module ClassMethods
      def configure_race(methods)
        [:participant_count, :run].each do |method_symbol|
          define_method method_symbol, methods[method_symbol]
        end
      end
    end
  end

  class RunnerRace < Struct.new(:runner_count)
    include Race

    configure_race participant_count: ->() { runner_count },
      run: ->(index) { puts "I am runner number #{index}. I am running" }
  end

  class CarRace < Struct.new(:car_count)
    include Race

    configure_race participant_count: -> { car_count },
      run: ->(index) { puts "I am car number #{index}. I am going" }
  end

  RunnerRace.new(2).go!
  CarRace.new(2).go!

还有哪些其他解决方案?这种情况有共同的习惯用法吗?

4 个答案:

答案 0 :(得分:3)

为什么不利用Ruby是一种动态语言这一事实​​呢?

class Race
  attr_reader :participants

  def initialize(participants)
    @participants = participants
  end

  def go!
    participants.each_with_index.map do |index,participant|
      Thread.new do
        participant.run(index)
      end
    end.each(&:join)
  end
end

class CarEntry
  def run(index) 
    puts "I am car number #{index}. I am going"
  end
end

没有必要在比赛中运行任何东西。扩展超类。比赛中的所有任何事情都必须能够比赛,即有一个

run(index)

做某事的方法。

答案 1 :(得分:1)

在红宝石中,我倾向于想到&#39; AbstractRace&#39;更像是一个班级的角色。角色最好封装在模块中(正如您在第一次回复中所建议的那样)。

但是,如果您尝试提供一般的ruby解决方案,我建议删除对ActiveSupport :: Concern的引用。此模块来自Rails,可能并非在所有ruby环境中都可用。

答案 2 :(得分:0)

你在那里写的东西没有错,我不完全确定你不满意。

话虽如此,这是Ruby,并且无需在run类上定义AbstractRace方法。如果那是什么困扰你,那么就不要这样做。

您放在那里的原因是为了向您自己和其他开发人员展示可能正在处理Race应该具有run方法的代码。它定义了界面。它绝对不是必需的,但它是&#34;适当的&#34;制作面向对象的类层次结构的方法。

答案 3 :(得分:0)

我认为你错过了抽象类的重点

Java之类的语言使用抽象类,因为他们想要:

  • 在一个地方(公共基类)和
  • 收集派生类的公共代码
  • 防止实例化此公共基类,因为它是基类。

如果您想实现相同的目标,可以通过以下方式实现:

  • 使用公共代码
  • 创建基类
  • 给它一个私人构造函数:

``` 类AbstractRace   私人:

def初始化   结束 结束 ```

人们使用抽象类的另一个原因与接口相同:他们希望保证派生类上存在某些方法。

不幸的是,没有像Ruby这样的结构;事实上,这是“不像Ruby一样的”。在Ruby中,我们依赖于“鸭子打字”,这意味着“如果它嘎嘎叫,它就是一只鸭子!”观察:

class Car 
  def drive
    return 'VROOOM!'
  end
end

class Duck
  def quack
    return 'Quack quack!'
  end
end

test_objs = [Car.new, Duck.new]

test_objects.each do |some_obj|
  if some_obj.respond_to?(:quack)
    puts "#{some_obj} is a duck! #{some_obj.quack}"
  else
    puts 'Not a duck, sorry.
  end
end

这将输出类似<Duck:0x123456> is a duck! Quack quack!

的内容

这是依靠“鸭子打字”,通过在使用之前检查方法的存在。这是Ruby中最接近的习语。