【问题标题】:What is the best way to dry out this ruby code?干掉这个红宝石代码的最佳方法是什么?
【发布时间】:2019-11-14 22:15:05
【问题描述】:

我想干掉这段代码。最好的解决方案是像在 rails 中使用 before_action 方法一样吗?

class Direction
    attr_accessor :dir

    def initialize(dir)
     @dir = dir
    end

    DIRECTIONS = %w[N E S W]

    def turn_left
     d = DIRECTIONS.find_index(dir)
     @dir = DIRECTIONS.rotate!(d-1).first
    end

    def turn_right
     d = DIRECTIONS.find_index(dir)
     @dir = DIRECTIONS.rotate!(d+1).first
    end
end

【问题讨论】:

  • 一些控制器代码会很有用。
  • 将此处的状态与方向概念分开可能更有意义。这种混淆了两件事,即基本方向的定义和方向状态的存储。
  • 您可以使用dir 而不是@dir 来简化您的方法,因为您有attr_accessor :dir
  • “是否可以像在 rails 中一样在 ruby​​ 中进行操作”?您确实意识到 Rails IS Ruby 代码,对吗?所以,当然有可能。
  • 调用rotate! 会修改DIRECTIONS 常量所指的数组。不要那样做——常量不应该是偶然的。

标签: ruby-on-rails ruby dry


【解决方案1】:

我建议使用哈希,主要是为了便于阅读。

class Direction
  NEXT_LEFT  = { 'N'=>'W', 'W'=>'S', 'S'=>'E', 'E'=>'N' }
  NEXT_RIGHT = NEXT_LEFT.invert

  attr_reader :dir

  def initialize(dir)
    @dir = dir
  end

  def turn_left
    turn(NEXT_LEFT)
  end

  def turn_right
    turn(NEXT_RIGHT)
  end

  private

  def turn(nxt)
    @dir = nxt[@dir]
  end
end

d = Direction.new('N')
d.dir
  #=> "N" 
d.turn_left
  #=> "W" 
d.turn_left
  #=> "S" 
d.turn_right
  #=> "W" 

注意:

NEXT_RIGHT
  #=> {"W"=>"N", "S"=>"W", "E"=>"S", "N"=>"E"}

【讨论】:

  • 又好又简单,也使它非常自我记录!我会为您的代码提供答案,因为它尽可能干燥。我可能也会添加一个turn_around 方法。 :-)
【解决方案2】:
# frozen_string_literal: true

class Direction
  DIRECTIONS = %w[N E S W].freeze
  OPERATIONS = { left: :-, right: :+ }.freeze
  private_constant :DIRECTIONS, :OPERATIONS

  def initialize(dir)
    @dir = dir
  end

  OPERATIONS.keys.each do |turn_direction| # turn_left, turn_right 
    define_method "turn_#{turn_direction}" do
      turn(turn_direction)
    end
  end

  private

  attr_reader :dir

  def direction_index
    DIRECTIONS.find_index(dir)
  end

  def turn(operation)
    DIRECTIONS.rotate(direction_index.public_send(OPERATIONS[operation], 1)).first
  end
end

p Direction.new('N').turn_left  # "W"
p Direction.new('E').turn_left  # "N"
p Direction.new('S').turn_left  # "E"
p Direction.new('W').turn_left  # "S"
p Direction.new('N').turn_right # "E"
p Direction.new('E').turn_right # "S"
p Direction.new('S').turn_right # "W"
p Direction.new('W').turn_right # "N"

你可以:

  • freeze 你的常量以避免修改。
  • 如果常量没有在Direction 类之外使用,请更改它们的可见性。
  • 如果您只在类本身中使用dir,请更改它的可见性。
  • 创建一个OPERATIONS 哈希,它定义了方向和它将用来返回下一个方向的操作。
  • 遍历OPERATIONS 键以动态定义方法turn_leftturn_right
  • 定义一个direction_index方法,使用dir返回DIRECTIONS中的索引。
  • 定义一个turn方法,它接收一个operation参数:
    • 使用operation,您可以从OPERATIONS 获取操作,该操作告诉您如何旋转(正或负)。
    • -+ 方法应用于direction_index 的结果,您将获得要旋转的参数。
    • 然后在DIRECTIONS 上调用rotate 并获取第一个元素。

【讨论】:

    【解决方案3】:

    很多很好的答案,但一个简单的直接解决方案是将两种方法之间共有的部分分解为轮流方法并传入1-1

    class Direction
      attr_accessor :dir
    
      def initialize(dir)
        @dir = dir
      end
    
      DIRECTIONS = %w[N E S W]
    
      def turn(delta_d)
        d = DIRECTIONS.find_index(dir)
        @dir = DIRECTIONS.rotate!(d + delta_d).first
      end
    
      def turn_left
        turn(-1)
      end
    
      def turn_right
        turn(1)
      end
    
    end
    

    【讨论】:

      【解决方案4】:

      您始终可以实现独立于状态的方向图:

      class DirectionMap
        def initialize(*list)
          # Create a Hash mapping table with left and right directions
          # pre-computed. This uses modulo to "wrap" the array around.
          @directions = list.map.with_index do |dir, i|
            [ dir, [ list[(i - 1) % list.length], list[(i + 1) % list.length] ] ]
          end.to_h
        end
      
        # These methods use dig to avoid blowing up on an invalid direction,
        # instead just returning nil for garbage input.
        def left(dir)
          @directions.dig(dir, 0)
        end
      
        def right(dir)
          @directions.dig(dir, 1)
        end
      end
      

      您现在可以在哪里导航任意指南针映射:

      map = DirectionMap.new(*%w[ N E S W ])
      
      map.left('N') # => 'W'
      map.left(map.left('N')) # => 'S'
      
      map.right('N') # => 'E'
      map.right(map.left('N')) # => 'N'
      

      所以你也可以使用%w[ N NE E SE S SW W NW ]

      【讨论】:

        【解决方案5】:

        我认为您可以避免每次转弯时创建新数组的所有工作(通过调用rotate 就可以了)。只需将当前方向存储为数组中其字母的索引即可。 Turning 只是索引上的模运算(注意在 Ruby -1 % 4 == 3 中)。而当你想要方向的字母时,只需使用索引从数组中获取它。

        class Direction
          DIRECTIONS = %w[N E S W].freeze
        
          def initialize(dir)
            self.dir = dir
          end
        
          # dir getter
          def dir
            DIRECTIONS[@dir_index]
          end
        
          # dir setter
          def dir=(dir)
            @dir_index = DIRECTIONS.index(dir)
          end
        
          # turning logic
          def turn(delta)
            @dir_index = (@dir_index + delta) % DIRECTIONS.size
            dir
          end
        
          def turn_left
            turn(-1)
          end
        
          def turn_right
            turn(1)
          end
        end
        
        p Direction.new('N').turn_left   #=> "W"
        p Direction.new('E').turn_left   #=> "N"
        p Direction.new('S').turn_left   #=> "E"
        p Direction.new('W').turn_left   #=> "S"
        p Direction.new('N').turn_right  #=> "E"
        p Direction.new('E').turn_right  #=> "S"
        p Direction.new('S').turn_right  #=> "W"
        p Direction.new('W').turn_right  #=> "N"
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-19
          • 1970-01-01
          相关资源
          最近更新 更多