为啥我们要在 Ruby 的类中放置一个模块?

Posted

技术标签:

【中文标题】为啥我们要在 Ruby 的类中放置一个模块?【英文标题】:Why would we put a module inside a class in Ruby?为什么我们要在 Ruby 的类中放置一个模块? 【发布时间】:2011-04-02 10:16:14 【问题描述】:

在 Ruby 中,我发现为了命名空间而将类放在模块中会很有用。我还看到可以将模块放在类中。但我不明白你为什么要这么做。

模块通常混合到类中,对吗?那么,在类中定义模块的目的是什么?

【问题讨论】:

模式在 Lita ChatOps Handler 类中找到。 github.com/litaio/lita/blob/master/lib/lita/handler/common.rb 【参考方案1】:

我想这实际上只是将一个类用作命名空间,这有时比将所有内容都放在一个模块中更方便。我在实践中从未见过这种情况,但无论哪种方式,它都是完全有效的 Ruby 代码。

我能想到的唯一真实场景是在类中使用 EventMachine:

class Api
  def initialize
    EM.start_server "0.0.0.0", 8080, Server
  end

  module Server
    def receive_data (data)
      # do stuff
    end
  end
end

【讨论】:

【参考方案2】:
class Image
    module Colors
        Red = ...
        Blue = ...
    end
    include Colors
end

include Image::Colors

Image.new.set_pixel x, y, Red 

【讨论】:

【参考方案3】:

我们可以在编写类似猿的代码时使用它:

class DrugDealer
  module Drug
    def happy?; true; end
  end

  def approach(victim)
    victim.extend Drug
  end
end

o = Object.new
DrugDealer.new.approach(o)
o.happy? # => true

另一个在现实世界中更实用的例子是只有子类应用的 mixin。

当事物的某些方面应用于某些子类而其他方面适用于其他子类时,这很有用,而这些方面的应用方式没有足够的顺序来为清晰的类层次结构让路(树)。考虑多重继承!一个简化的例子:

class Person
  def handshake
    :sloppy
  end

  def mind_contents
    :spam
  end

  module Proper
    def handshake
      :firm
    end
  end

  module Clever
    def mind_contents
      :theories
    end
  end
end

class Professor < Person
  include Proper
  include Clever

  # ...
end

等等。有点好,当明智地使用时。即使是超级调用和构造函数(虽然我在这里没有定义)也会按照我想要的方式流过所有的 mixin 和类。

【讨论】:

【参考方案4】:

此后,我在一个具有复杂命名空间的大型 Rails 应用程序中遇到了一个用例。一个简化的例子:

# app/models/invoice/dependents/item.rb
class Invoice
  module Dependents
    class Item
      # Define invoice item
    end
  end
end

这里的Invoice 是它自己的一个类,但也是它的依赖项的一个很好的命名空间。我们不能说module Invoice,因为该常量已经定义为一个类,但我们仍然可以将它用作命名空间。

重大警告

如果您使用一个类作为命名空间,并且您正在使用 Rails,请确保您不要意外地在其他地方声明该类。自动加载会毁了你的一天。例如:

# app/helpers/invoice/dependents/items_helper.rb
class Invoice       # This line will cause you grief
  module Dependents
    module ItemsHelper
      # view helper methods
    end
  end
end

class Invoice 在此文件中声明的事实创建了加载顺序依赖关系;如果此文件的 class Invoice 行在您的预期类定义之前执行,您的预期类定义可能无法正常工作。在这个例子中,如果 Invoice 已经被声明为没有父类,我不能声明 Invoice sublcasses ActiveRecord::Base

可以在另一个文件的顶部要求您的“真实”类定义文件,但至少在 Rails 自动加载场景中,如果您这样做,您将有更少的争论:

# app/helpers/invoice/dependents/items_helper.rb
module Invoice:Dependents::ItemsHelper  
  # view helper methods
end

使用这种语法,Rails 将看到 Invoice 常量并使用自动加载来查找它,在您的模型文件中找到它并按照您想要的方式定义它。

【讨论】:

很好地解释了这个警告——我也碰到了。但是,如果您采用建议的方法 (module Invoice::Dependents::ItemsHelper),如果您想引用 Invoice::Dependents 范围内的其他类怎么办?我发现我必须包含完整的类名 (Invoice::Dependents::OtherClass.method),这非常冗长。 我相信随着 Zeitwerk 自动装载机的引入,这个警告已经得到解决。经典自动加载器的弃用周期将从 rails 6.1 开始

以上是关于为啥我们要在 Ruby 的类中放置一个模块?的主要内容,如果未能解决你的问题,请参考以下文章

为啥onclick=""引号中放置的方法后面有时候加;有时候不加啊

为啥我不能在 uitableview 单元格中放置一个 nsarray

为啥在 UIScrollView 中放置视图时需要 6 个约束?

如何在 Eclipse 的类路径中放置一个 jar? [复制]

C++ 为啥在向量段错误中放置对象?

为啥我们要在 CodeIgniter 中设置 $system_path?