在 Ruby 中使用 2 个助手构建函数的惯用方法

Posted

技术标签:

【中文标题】在 Ruby 中使用 2 个助手构建函数的惯用方法【英文标题】:Idiomatic ways to structure a function with 2 helpers in Ruby 【发布时间】:2021-09-24 06:00:10 【问题描述】:

我编写了一个解决自恋数字 kata on codewars 的方法。

在编写了一个函数后,我提取了两个辅助函数以将我的行数保持在最多 5 行 (Sandi Metz' Rules For Developers)。

这产生了 3 个函数:

def digits(number)
  number
    .to_s
    .chars
    .map(&:to_i)
end

def checksum(digits, exp)
  digits
    .map  |d| d**exp 
    .reduce(:+)
end

def narcissistic?(number)
  digits = digits(number)
  exp = digits.length
  checksum = checksum(digits, exp)
  checksum == number
end

现在,我想假设这段代码应该被添加到一个更大的实际项目中。我的问题是应该如何在 Ruby 中惯用地完成此操作。

一般来说,我有两个要求:

    代码应该以某种方式命名空间(考虑到实际项目)。 应该清楚narcissistic?公共API 函数 - 处于更高级别,而其他两个函数digitschecksum 是在较低级别的抽象上。

到目前为止,我的推理是:这段代码并不真正需要 OOP。但在 Ruby 中,将某些内容放入命名空间的唯一方法是创建 ClassModule

Module 可能是更好的选择?不过,我不确定我是否应该更喜欢:

module MathUtils::NarcissisticNumbers
  def self.narcissistic?(number)
    ...
  end

  private
  ...
end

module MathUtils::NarcissisticNumbers
  def narcissistic?(number)
    ...
  end

  private
  ...
end

您如何将这段代码引入到 Ruby 项目中?如果您知道最佳实践解决方案,请告诉我! :)

任何其他指针也将受到高度赞赏。

【问题讨论】:

【参考方案1】:

在我看来,这取决于您的方法的目的,考虑narcissistic 方法的两个名称:

    narcissistic?(number): 这让我觉得有一个外部类负责检查输入的数字是否自恋。

    narcissistic?: 这让我想到班级本身能够检查它是否自恋。

所以在情况 1 中,假设您有一个包含模块 MathUtils::NarcissisticNumbers 的类 Code,如果该模块不支持类方法,那么只有类代码 can_do 的实例检查 narcissistic,然后方法名称应属于上述情况2。

另一方面,如果模块支持类方法,那么方法名应该属于情况1,但是,假设你有一个类Money需要检查narcissistic它的值,如果你使用@987654330 @这会让其他人感到困惑(至少他们需要知道Code是什么),但是如果您使用MathUtils::NarcissisticNumbers.narcissistic?(money.value)完全有意义,其他人会立即明白这是一种检查数字的方法。

我建议你让MathUtils::NarcissisticNumbersmodule_function 并为narcissistic? 创建另一个模块

module MathUtils::NarcissisticNumbers
  module_function
  def is_narcissistic?(number)
  end
end

module Narcissistic
 def narcissistic?
   MathUtils::NarcissisticNumbers.is_narcissistic?(self.value)
 end
end

class Code
 include Narcissistic
end

class Money
 include Narcissistic
end

code = Code.new(...)
code.narcissistic?

# for those classes that only check narcissistic? internally
# then you can include MathUtils::NarcissisticNumbers
# since is_narcissistic?(number) become a private method

class FormatNumber
 include MathUtils::NarcissisticNumbers
 def format(number)
   if is_narcissistic?(number)
    # ...
   else
    # ...
   end
 end
end

# you can use MathUtils::NarcissisticNumbers wherever you want (as helper)
# on other classes that not include Narcissistic, including views , ...
<% if MathUtils::NarcissisticNumbers.is_narcissistic?(input) %>

【讨论】:

【参考方案2】:

我同意 Lam 已经写的大部分内容。但是,我会先提取一个您在模块中使用的类。类使处理数据变得更加容易(并遵循建议,您的方法应该最大为 5LOC)。

class MathUtils::NarcissisticNumber
  def initialize(number)
    @number = number
  end

  def valid?
    checksum == number
  end

  private
  
  attr_reader :number

  def checksum
    digits.map  |d| d**exponent .reduce(:+)
  end

  def digits
    @digits ||= number.to_s.chars.map(&:to_i)
  end

  def exponent
    @exponent ||= digits.length
  end
end

通过使用类,我们能够删除所有方法参数和临时变量。我们现在可以在 Liam 建议的帮助模块中使用这个类。

module MathUtils::NarcissisticNumbers
  def narcistic?(number)
    NarcissisticNumber.new(number).valid?
  end
end

【讨论】:

以上是关于在 Ruby 中使用 2 个助手构建函数的惯用方法的主要内容,如果未能解决你的问题,请参考以下文章

是否有一种惯用的方式来操作 Ruby 中的 2 个数组?

从 Golang 中的数组中选择元素的最惯用方法?

Clojure - 1 个函数的 2 个版本。哪个更惯用?

ruby 在多个数组上映射一个函数

什么是“pythonic”的 Ruby 等价物? [关闭]

惯用的 Python:'times' 循环