从私有实例方法调用私有类方法

时间:2013-08-10 01:32:15

标签: ruby oop

我是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 个答案:

答案 0 :(得分:4)

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

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

深入研究这种模式的一些资源:

答案 1 :(得分:1)

您可以使用

model = MyModel.new
model.send :init_some_dependencies

绕过方法可见性检查。

答案 2 :(得分:-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)

但红宝石并没有严格执行隐私。例如,

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]!

答案 3 :(得分:-3)

当然,确实如此。

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