Ruby - 从另一个方法中提取我的小方法(将字符串转换为两个数字和一个符号)

Ruby - Extracting my smaller methods out from another method (transforming a string into two numbers and a symbol)

我有一个方法(land_connected_rover(coordinates))接受一个参数(包含两个数字和一个字母的字符串,space分隔)结束根据这个执行提示,我的问题是我很难从这个方法中提取我的分配,我尝试了私有方法但是一旦我这样做,我注入的 类 的 none 可以再访问这些变量。我想要一个优雅的、基于 SRP 的解决方案,而不是当前凌乱的解决方案...呃,对不起,伤害了你的眼睛!

'x'、'y' 和 'position' 变量名称对其他 类 很重要,因为它们依赖于这些提示...

我想提取将 x、y 和位置变量名称分配给字符串右侧部分的步骤。

class Controller

  attr_reader :current_rover, :current_surface

  def initialize
    @current_surface = nil
    @current_rover ||= []
  end

  def connect_to_surface(destination)
    @current_surface = destination
  end

  def connect_to_rover(rover)
    @current_rover = rover
  end

  def land_connected_rover(coordinates)
    coordinates = coordinates.delete(' ')
    x = coordinates[0].to_i
    y = coordinates[1].to_i
    position = coordinates[2].to_sym
    self.current_rover.x_coordinates = x
    self.current_rover.y_coordinates = y
    self.current_rover.position = position
    add_to_grid(x,y)
  end

  def navigate(command)
    self.current_rover.read_input(command)
  end

  private

  def add_to_grid(x,y)
    @current_surface.record_on_map(x,y)
  end

  def turn_on_rover
    @current_rover.online = true
  end
end

非常感谢您的帮助!抱歉,如果我问错了问题,我是新手..

关于 opportunity cost 的注释。有可能过度考虑。担心这种方法可能不值得你花时间。这是一种简单语句的八行方法,易于理解并且有效。大概它已经过测试,但如果没有,那是你最好花时间做的事情。

例如,您将 @current_rover 初始化为一个列表,但它被用作用户对象。我怀疑如果用户不设置 @current_rover,很多事情都会以令人困惑的方式发生。那将是值得花时间的事情。您可以进行测试,尝试使用新初始化的对象并确保它们产生有意义的异常,或者您可以 side-step 所有这些复杂性并决定必须使用流动站对其进行初始化。

让我们把重构当作练习。


首先,让我们尝试描述 land_connected_rover 所做的所有事情,方法是对每一块事情进行评论以解释它们所做的事情。

  def land_connected_rover(coordinates)
    # Parse coordinates.
    coordinates = coordinates.delete(' ')
    x = coordinates[0].to_i
    y = coordinates[1].to_i
    position = coordinates[2].to_sym

    # Set rover coordinates.
    self.current_rover.x_coordinates = x
    self.current_rover.y_coordinates = y
    self.current_rover.position = position

    # Add something to the grid for some reason.
    add_to_grid(x,y)
  end

像这样的评论对于提取方法来说是非常好的。我们需要一些东西来解析坐标。设置坐标的东西。以及将坐标添加到网格的东西。我们已经有了最后一个,所以提取另外两个。

    def parse_coordinates(input)
       input = input.delete(' ')

       return {
           x:   coordinates[0].to_i,
           y:   coordinates[1].to_i,
           position: coordinates[2].to_sym
       };
    end

    def set_rover_coordinates(coordinates)
        @current_rover.x_coordinates = coordinates[:x]
        @current_rover.y_coordinates = coordinates[:y]
        @current_rover.position = coordinates[:position]
    end

现在可以对这些进行记录、重用和单元测试。当坐标未解析或未设置 @current_rover 时,测试可能会显示需要添加错误处理。

我使用 @current_rover 来与使用实例变量而不是访问器进行内部访问的其余代码保持一致。各有各的道理,选一个吧。

然后把它们放回原处。

  def land_connected_rover(input)
    set_rover_coordinates( parse_coordinates(input) )
    add_to_grid(x,y)
  end

从那里我们可以进行更多观察。为什么 Controller 编写方便的方法来设置 Rover 的属性? set_rover_coordinates 应该会搬到 Rover。

  def land_connected_rover(input)
    @current_rover.set_coordinates( parse_coordinates(input) )
    add_to_grid(x,y)
  end

谁应该解析坐标?我可以看到 Rover 和 Controller 都需要这个的充分理由。这表明您需要坐标 class.

  # In Controller
  def land_connected_rover(input)
    @current_rover.set_coordinates( Coordinate.from_a(input) )
    add_to_grid(x,y)
  end

  # In Rover
  def set_coordinates(coordinates)
    @x_coordinates = coordinates.x
    @y_coordinates = coordinates.y
    @position = coordinates.position
  end

现在我们观察到漫游者的坐标不是一堆属性,它应该有一个采用坐标对象的坐标属性。

  # In Rover
  attr :coordinates

  # In Controller
  def land_connected_rover(input)
    @current_rover.coordinates( Coordinate.from_a(input) )
    add_to_grid(x,y)
  end

现在我们有了 Coordinate 对象,可以向 Controller 传递 Coordinate 对象,而不必担心对其输入进行规范化。

  def land_connected_rover(coordinates)
    @current_rover.coordinates( coordinates )
    add_to_grid(x,y)
  end

让调用者处理规范化。

  controller.land_connected_rover( Coordinate.from_a([10,20,30] ) )

既然您有了 Coordinate 对象,调用者就可以使用它们了,大部分转换需求可能都会消失。


从这里开始,我的下一个问题是漫游者和表面之间的坐标重复。 Rover 有自己的坐标,Surface 似乎独立地跟踪 Rover 的坐标。我假设两者都必须保持同步?如果是这样,那会增加复杂性并冒着错误的风险。或者,也许 Surface 只是在跟踪漫游者最初着陆的位置?这是需要考虑的事情。


请注意,在我开始重构之前,其中大部分内容并不明显。我最初停在 land_connected_rover 的第一次重构,但后来又想了想,想出了坐标 class,然后从那里级联。我认为最终的结果要好得多,远远超出了我一开始的预期。

所以...是的,有时确实担心八行法。 :)

最后我得到了这个解决方案:

class Controller

  attr_reader :current_rover, :current_surface

  def initialize
    @current_surface ||= nil
    @current_rover ||= nil
  end

  def connect_to_surface(destination)
    @current_surface = destination
  end

  def connect_to_rover(rover)
    @current_rover = rover
  end

  def is_mission_ready?
    @current_surface != nil && @current_rover != nil
  end

  def land_connected_rover(coordinates)
    raise 'Mission is not ready to launch yet!' if !is_mission_ready?
    x, y, position = parse_coordinates(coordinates)
    current_rover.x_coordinates = x
    current_rover.y_coordinates = y
    current_rover.position = position
    add_to_grid
  end

  def navigate(command)
    raise 'Navigation is not ready to start yet!' if !is_mission_ready?
    current_rover.read_input(command)
  end

  private

  def parse_coordinates(coordinates)
    fields = coordinates.split(" ")
    [fields[0].to_i, fields[1].to_i, fields[2].to_sym]
  end

  def add_to_grid
    x = current_rover.x_coordinates
    y = current_rover.y_coordinates
    position = current_rover.position
    @current_surface.record_on_map(x,y)
    puts "Rover landed, currently facing: #{current_rover.position}, x-coordinates: #{x}, y-coordinates: #{y}"
    @current_surface.grid
  end
end