7

私はRubyが初めてで、C#の世界から来ました。C# では、次のようなことを行うことは合法です。

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

  private void PrivateMethod()
  {
     PrivateStaticMethod();
  }

  private static void PrivateStaticMethod()
  {
  }
}

Rubyで同様のことを行うことは可能ですか?

ちょっとしたコンテキスト: 私は Rails アプリを持っています... モデルの 1 つに、いくつかの依存関係を設定するプライベート メソッドがあります。モデルの初期化されたインスタンスを作成するクラス メソッドがあります。従来の理由により、正しく初期化されていないモデルのインスタンスがいくつかあります。同じ初期化ロジックを実行したい「初期化されていない」インスタンスを初期化するインスタンス メソッドを追加しました。重複を避ける方法はありますか?

サンプル:

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 の両方が、ブロック内の自己をレシーバーに変更します

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 に答える