【问题标题】:Ruby abstraction红宝石抽象
【发布时间】:2009-12-22 05:45:31
【问题描述】:

我是 Ruby 新手,主要来自 C# 和 ActionScript 3(以及其他语言)。我对抽象功能很好奇。具体来说,包装和抽象 Ruby 的 FTP 和 SFTP 库。

我四处寻找,发现了一个名为 Backup 的宝石。它确实引起了我的注意,因为它支持通过 S3、SCP、SFTP 和 FTP 备份内容。所以我想,“哇,这是一个完美的例子!”我开始浏览源代码,但后来我遇到了如下代码:

case backup.procedure.storage_name.to_sym
  when :s3    then records = Backup::Record::S3.all   :conditions => {:trigger => trigger}
  when :scp   then records = Backup::Record::SCP.all  :conditions => {:trigger => trigger}
  when :ftp   then records = Backup::Record::FTP.all  :conditions => {:trigger => trigger}
  when :sftp  then records = Backup::Record::SFTP.all :conditions => {:trigger => trigger}
end

view the full source on GitHub

到处都是 case/when 语句!如果我在 C# 中对此进行攻击,我会编写一个协议接口(或抽象类)并让 FTP 和 SFTP 实现它。然后我的客户端类将只传递一个协议实例而不关心实现。零开关/外壳。

如果在这种情况下使用 Ruby 进行编码,我希望能提供一些关于最佳实践的指导。

【问题讨论】:

    标签: ruby oop ftp sftp abstraction


    【解决方案1】:

    你也可以在 Ruby 中这样做

    由于动态类型,Ruby 不需要接口。就此而言,它不需要原型、签名或模板,甚至子类,虽然存在,但并不是绝对必要的。

    当我说“不需要”时,我只是说您所指的设计模式可以直接在 Ruby 中实现。因为在“编译时”没有强制执行任何调用限制,所以任何依赖于接口或任何多态风格的设计模式都可以在 Ruby 中直接使用。

    是的,看起来该包并没有充分利用可能的抽象,但也许 (a) 没关系,只要它有效。毕竟,您不需要输入它,或者 (b) 使用的简单组合模式有一些并非立即显而易见的好处。

    【讨论】:

      【解决方案2】:

      我认为有几种方法可以优雅地做到这一点。一是使用上面建议的 TK 发送。另一种是使用“method_missing”,Ruby在找不到现有方法时调用的方法。

      Metaprogramming Ruby 很好地涵盖了这两个选项。幸运的是,它位于在线免费示例章节中(如果您想了解更多信息,我推荐这本书)。

      很抱歉没有给你一个代码 sn-p,但请仔细阅读它,看看它是否有帮助。

      【讨论】:

        【解决方案3】:

        大多数时候,OO 语言中的 case 表达式表明您没有正确使用多态性。在这种情况下,我会这样写:

        backup.procedure.storage_class.all :conditions => {:trigger => trigger}
        

        storage_class 返回相应的类。 (其实我更愿意将storage_class作为备份本身的属性,但我不知道这在这个库的设计中是否实用。)

        【讨论】:

          【解决方案4】:

          公平地说,上面的代码非常明确。除了将其实现为对象的层次结构之外,您还可以发疯并做类似的事情

          storage_method = backup.procedure.storage_name.upcase
          
          records = eval("Backup::Record::#{storage_method}.all :conditions => {:trigger => trigger}"
          

          我并不是说这实际上是正确的,但希望它能说明我的观点,即简洁的代码并不总是比显式的代码更好。 Ruby 可能和 C 一样危险 :-)

          【讨论】:

          • 是的,eval 比 case/whens 更让我害怕 :)
          【解决方案5】:

          可能有更好的方法,但这是我的想法。

          ALLOWD_OPTIONS = [:s3, :scp, :ftp ,:sftp].freeze
          type = ALLOWD_OPTIONS.detect { |e| e == backup.procedure.storage_name.to_sym }
          if type
            records = send(%s"Backup::Record::#{type.upcase}.all", :conditions => {:trigger => trigger})
          end
          

          if [:s3, :scp, :ftp, :sftp].include?(backup.procedure.storage_name.to_sym)
             records = __send__ (%s"Backup::Record::#{backup.procedure.storage_name.to_sym.upcase}.all", 
                                 :conditions => {:trigger => trigger})
          end
          

          如果您使用的是 Ruby 1.9,您可以使用 public_send() 来获得额外的安全性。我查看了有问题的文件。有很多案例陈述。您可以创建一个私有方法来执行我上面写的操作,以尽量减少重复。

          ALLOWD_OPTIONS = [:s3, :scp, :ftp ,:sftp].freeze
          
          records = send_method_with_type("all", backup.procedure.storage_name.to_sym, 
                                                 :conditions => {:trigger => trigger})
          
          private
          
          def send_method_with_type(method, type, *args)
            raise unless ALLOWD_OPTIONS.inlucde? type
            send(%s"Backup::Record#{type.upcase}.#{method}", args)
          end
          

          【讨论】:

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