7

我是 Ruby 新手,来自 C# 世界。在 C# 中,这样做是合法的:

public class Test
{
  public void Method()
  {
     PrivateMethod();
  }

  private void PrivateMethod()
  {
     PrivateStaticMethod();
  }

  private static void PrivateStaticMethod()
  {
  }
}

是否可以在 Ruby 中做类似的事情?

一点上下文:我有一个 Rails 应用程序......其中一个模型有一个私有方法,可以设置一些依赖项。有一个类方法可以创建模型的初始化实例。由于遗留原因,模型的某些实例未正确初始化。我添加了一个实例方法来初始化我想要执行相同初始化逻辑的“未初始化”实例。有没有办法避免重复?

样本:

class MyModel < ActiveRecord::Base

  def self.create_instance
    model = MyModel.new
    model.init_some_dependencies # this fails
    model
  end

  def initialize_instance
    // do some other work
    other_init
    // call private method
    init_some_dependencies
  end

  private

  def init_some_dependencies
  end

end

我试图将我的私有方法转换为私有类方法,但仍然出现错误:

class MyModel < ActiveRecord::Base

  def self.create_instance
    model = MyModel.new
    MyModel.init_some_dependencies_class(model)
    model
  end

  def initialize_instance
    # do some other work
    other_init
    # call private method
    init_some_dependencies
  end

  private

  def init_some_dependencies
     MyModel.init_some_dependencies_class(self) # now this fails with exception
  end

  def self.init_some_dependencies_class(model)
    # do something with model
  end

  private_class_method :init_some_dependencies_class

end
4

4 回答 4

5

首先让我尝试解释为什么代码不起作用

class MyModel < ActiveRecord::Base

  def self.create_instance
    model = MyModel.new
    # in here, you are not inside of the instance scope, you are outside of the object
    # so calling model.somemething can only access public method of the object.
    model.init_some_dependencies
    ...
  end
  ...

您可以使用 绕过该方法的私人调用model.send :init_some_dependencies。但我认为在这种情况下可能有更好的解决方案。

我猜这init_some_dependencies可能包含更多的业务/领域逻辑而不是持久性。这就是为什么我建议将此逻辑提取到“域对象”(或有人称之为服务对象)中的原因。这只是一个包含域逻辑的普通 ruby​​ 对象。

这样,您可以将持久性逻辑与 ActiveRecord 分离,将域逻辑与该类分离。因此,您不会使 ActiveRecord 模型膨胀。并且您无需 ActiveRecord 即可获得测试域逻辑的好处。这将使您的测试更快。

你可以创建一个文件说 `lib/MyModelDomain.rb'

class MyModelDomain
  attr_accessor :my_model

  def initialize(my_model)
    @my_model = my_model    
  end

  def init_some_dependencies
    my_model.property = 'some value example' 
  end
end

现在你可以用这个对象说这样的话

class MyModel < ActiveRecord::Base

  def self.create_instance
    model = MyModel.new
    domain = MyModelDomain.new(model)
    domain.init_some_dependencies
    domain.my_model
  end

  def initialize_instance
    # do some other work
    other_init

    domain = MyModelDomain.new(self)
    domain.init_some_dependencies
  end
end

initialize_instance如果您认为有必要,您可能还想移动

深入了解此模式的一些资源:

于 2013-08-10T02:36:07.000 回答
2

您可以使用

model = MyModel.new
model.send :init_some_dependencies

绕过方法可见性检查。

于 2013-08-10T02:13:13.770 回答
-1

在 C# 中,这样做是合法的:

public class Test
{
  public void Method()
  {
     PrivateMethod();
  }

  private void PrivateMethod()
  {
     PrivateStaticMethod();
  }

  private static void PrivateStaticMethod()
  {
  }
}

是否可以在 Ruby 中做类似的事情?

是的:

class Test

  def method
    private_method()
  end


  def self.greet
    puts 'Hi'
  end

  private_class_method :greet


  private

  def private_method
    self.class.class_eval do
      greet
    end
  end

end

Test.new.method
Test.greet

--output:--
Hi
1.rb:23:in `<main>': private method `greet' called for Test:Class (NoMethodError)

但是 ruby​​ 并没有严格执行隐私。例如,

class Dog
  def initialize
    @private = "secret password"
  end
end

puts Dog.new.instance_variable_get(:@private)

--output:--
secret password

ruby 让您可以通过一些额外的努力自由地访问私人事物:

Test.new.method

Test.class_eval do
  greet
end

--output:--
Hi
Hi

在 ruby​​ 中,私有方法仅意味着您不能显式指定该方法的接收者,即方法的左侧不能有名称和点。但在 ruby​​ 中,没有接收者的方法隐式使用 self 作为接收者。因此,要调用私有方法,您只需创建一个上下文,其中 self 是正确的接收者。class_eval 和 instance_eval 都将块内的 self 更改为它们的接收器,例如

some_obj.instance_eval do
  #Inside here, self=some_obj

  #Go crazy and call private methods defined in some_obj's class here

end

您可以将这些规则应用于这种情况:

(ahmy wrote:)

First let me try to explain why the code does not work

    class MyModel < ActiveRecord::Base

      def self.create_instance
        model = MyModel.new
        # in here, you are not inside of the instance scope, you are outside of the object
        # so calling model.somemething can only access public method of the object.
        model.init_some_dependencies  # this fails
        ...   end   ...

“上下文这个”和“范围那个”——真是令人头疼。您只需要记住:您不能使用显式接收器调用私有方法。方法 init_some_dependencies 被定义为私有方法——但它有“模型”。写在它的左边。那是一个明确的接收者。砰! 一个错误。

这是一个解决方案:

class MyModel

  def self.create_instance
    #In here, self=MyModel
    puts self

    model = MyModel.new

    model.instance_eval do    #Changes self to model inside the block
      #In here, self=model
      init_some_dependencies  #Implicitly uses self as the receiver, so that line is equivalent to model.init_some_dependencies
    end

  end


  private

  def init_some_dependencies 
    puts "Dependencies have been initialized!"
  end
end

MyModel.create_instance

--output:--
MyModel
Dependencies have been initialized!

或者正如 ahmy 和 LBg 指出的那样,您可以使用 Object#send() 调用私有方法:

class MyModel

  def self.create_instance
    model = MyModel.new
    model.send(:init_some_dependencies, 10, 20)
  end


  private

  def init_some_dependencies(*args)
    puts "Dependencies have been initialized with: #{args}!"
  end
end


MyModel.create_instance

--output:--
Dependencies have been initialized with: [10, 20]!
于 2013-08-10T11:47:50.980 回答
-3

实际上,确实如此。

Ruby 的一些 OO 策略(private&public关键字等)来自 C++,因此您可以获得几乎相同的用法。

于 2013-08-10T01:39:06.010 回答