1

我想知道用 ruby​​ 编写现有库的模块化扩展以改变现有方法的最佳方法是什么。它不应引入代码重复,应仅按需使用。

我试图完成的具体任务是扩展 ruby​​ 的Net::FTP模块以支持一些不太符合标准的服务器。这样的扩展应该与标准兼容库恕我直言完全分开。

我认为需要一个额外的文件会非常好,因为这甚至不会构成在原始代码中进行某种切换的必要性。因此,对于我们不太有天赋的 FTP 服务器研究员来说,额外的内容require 'net/ftp/forgiving'将使原始库更加宽容。

然后,相关文件可以利用 ruby​​ 的开放类和模块架构来修补 FTP 类。为了修复上面链接的古怪行为示例,我需要修补Net::FTP#mkdir. 看起来像这样:

#content of net/ftp/forgiving
require 'net/ftp'

module Net
  class FTP

    # mkdir that will accept a '250 Directory created' as a valid response
    def mkdir(dirname)
      begin
        original_mkdir(dirname)
      rescue FTPReplyError => e
        raise unless e.message.start_with? '250 Directory created'
        return ""
      end
    end

  end
end

然而,这需要以某种方式缓存原始代码Net::FTP#mkdirNet::FTP#original_mkdir保持代码干燥。这可能吗?您对如何改进这种修补/扩展方法有任何进一步的建议吗?或者甚至可能完全不同的方法?

4

1 回答 1

4

这被称为“monkeypatching”,并且正是用于以下用途的用例alias_method

alias_method :original_mkdir, :mkdir
def mkdir(dirname)
  begin
    original_mkdir(dirname)
  rescue FTPReplyError => e
    raise unless e.message.start_with? '250 Directory created'
    return ""
  end
end

尽管这是 Ruby 中常见的“习惯用法”,但这会破坏现有代码(甚至可能是内部代码Net),这些代码在这种情况下依赖于mkdir引发异常。您不能将这些更改require 'net/ftp/forgiving'仅限于文件。因此,创建一个子类而不是打开原始类会更干净:

module Net
  class ForgivingFTP < FTP
    # mkdir that will accept a '250 Directory created' as a valid response
    def mkdir(dirname)
      begin
        super(dirname)
      rescue FTPReplyError => e
        raise unless e.message.start_with? '250 Directory created'
        return ""
      end
    end
  end
end

或者更好的是,将其放置在自定义命名空间中!一个好的经验法则是:

尽可能子类化,必要时使用monkeypatch 。

(感谢@tadman)。在这种情况下,它似乎没有必要。

更新:跟进您的评论,如果您只想扩展Net::FTP类的特定实例,您可以扩展他们的单例类:

obj = Net::FTP.new
class << obj
  alias_method :original_mkdir, :mkdir
  def mkdir(dirname)
    #...
    original_mkdir(dirname)
    #...
  end
end
于 2012-02-13T17:26:57.853 回答