Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyの隠された宝石-委任者と転送可能

今日のRubyの標準ライブラリに隠された宝石の調査では、委任について見ていきます。

残念ながら、この用語は、他の多くの用語と同様に、何年にもわたって多少混乱しており、人によって意味が異なります。ウィキペディアによると:

委任とは、あるオブジェクト(受信者)のメンバー(プロパティまたはメソッド)を、別の元のオブジェクト(送信者)のコンテキストで評価することを指します。委任は、送信オブジェクトを受信オブジェクトに渡すことによって明示的に実行できます。これは、任意のオブジェクト指向言語で実行できます。または暗黙的に、機能の言語サポートを必要とする言語のメンバールックアップルールによって。

ただし、多くの場合、この用語は、引数として渡さずに別のオブジェクトの対応するメソッドを呼び出すオブジェクトを表すためにも使用されます。これは、より正確には「転送」と呼ばれます。

それが邪魔にならないように、記事の残りの部分では、「委任」を使用してこれらのパターンの両方を説明します。

委任者

標準ライブラリのDelegatorを見て、Rubyでの委任の調査を始めましょう。 いくつかの委任パターンを提供するクラス。

SimpleDelegator

これらの中で最も簡単で、私が実際に最も遭遇したのは、SimpleDelegatorです。 、初期化子を介して提供されたオブジェクトをラップし、不足しているすべてのメソッドをそのオブジェクトに委任します。これを実際に見てみましょう:

require 'delegate'
 
User = Struct.new(:first_name, :last_name)
 
class UserDecorator < SimpleDelegator
  def full_name
    "#{first_name} #{last_name}"
  end
end

まず、require 'delegate'が必要でした SimpleDelegatorを作成するには 私たちのコードで利用できます。 Structも使用しました 単純なUserを作成するには first_nameのクラス およびlast_name アクセサー。次に、UserDecoratorを追加しました full_nameを定義します 個々の名前の部分を1つの文字列に結合するメソッド。ここでSimpleDelegator どちらもfirst_nameではないので また、last_name 現在のクラスで定義されている場合は、代わりにラップされたオブジェクトで呼び出されます:

decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name
#=> "John Doe"

SimpleDelegator また、委任されたメソッドをsuperでオーバーライドすることもできます 、ラップされたオブジェクトの対応するメソッドを呼び出します。この例では、これを使用して、完全な名ではなくイニシャルのみを表示できます。

class UserDecorator < SimpleDelegator
  def first_name
    "#{super[0]}."
  end
end
decorated_user.first_name
#=> "J."
decorated_user.full_name
#=> "J. Doe"

委任者

上記の例を読んでいるときに、UserDecoratorがどのようになっているのか疑問に思いましたか? どのオブジェクトに委任するか知っていましたか?その答えはSimpleDelegatorにあります の親クラス-Delegator 。これは、__getobj__の実装を提供することにより、カスタム委任スキームを定義するための抽象基本クラスです。 および__setobj__ 委任ターゲットをそれぞれ取得および設定します。この知識を使用して、独自のバージョンのSimpleDelegatorを簡単に作成できます。 デモンストレーション用:

class MyDelegator < Delegator
  attr_accessor :wrapped
  alias_method :__getobj__, :wrapped
 
  def initialize(obj)
    @wrapped = obj
  end
end
 
class UserDecorator < MyDelegator
  def full_name
    "#{first_name} #{last_name}"
  end
end

これはSimpleDelegatorとは少し異なります __setobj__を呼び出す実際の実装 そのinitializeで 方法。カスタムデリゲータークラスはそれを必要としないため、そのメソッドを完全に省略しました。

これは、前の例とまったく同じように機能するはずです。そして実際にそうです:

UserDecorator.superclass
#=> MyDelegator < Delegator
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name
#=> "John Doe"

DelegateMethod

最後の委任パターンDelegate やや奇妙な名前のObject.DelegateClassが提供されます 方法。これにより、特定のクラスの委任クラスが生成されて返されます。委任クラスは、次から継承できます。

  class MyClass < DelegateClass(ClassToDelegateTo)
    def initialize
      super(obj_of_ClassToDelegateTo)
    end
  end

これは最初は混乱しているように見えるかもしれませんが、特に継承の右側に任意のRubyコードが含まれている可能性があるという事実は、実際には以前に調査したパターンに従います。つまり、SimpleDelegatorから継承するのと似ています。 。

Rubyの標準ライブラリは、この機能を使用してTempfileを定義します。 作業の多くをFileに委任するクラス 保存場所とファイル削除に関するいくつかの特別なルールを設定しながらクラス。同じメカニズムを使用して、カスタムのLogfileを設定できます。 このようなクラス:

class Logfile < DelegateClass(File)
  MODE = File::WRONLY|File::CREAT|File::APPEND
 
  def initialize(basename, logdir = '/var/log')
    # Create logfile in location specified by logdir
    path = File.join(logdir, basename)
    logfile = File.open(path, MODE, 0644)
 
    # This will call Delegator's initialize method, so below this point
    # we can call any method from File on our Logfile instances.
    super(logfile)
  end
end

転送可能

興味深いことに、Rubyの標準ライブラリは、Forwardableの形式で委任用の別のライブラリを提供します。 モジュールとそのdef_delegator およびdef_delegators メソッド。

元のUserDecoratorを書き直してみましょう Forwardableの例 。

require 'forwardable'
 
User = Struct.new(:first_name, :last_name)
 
class UserDecorator
  extend Forwardable
  def_delegators :@user, :first_name, :last_name
 
  def initialize(user)
    @user = user
  end
 
  def full_name
    "#{first_name} #{last_name}"
  end
end
 
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name
#=> "John Doe"

最も顕著な違いは、委任がmethod_missingを介して自動的に提供されないことです。 、ただし、代わりに、転送するメソッドごとに明示的に宣言する必要があります。これにより、クライアントに公開したくないラップされたオブジェクトのメソッドを「非表示」にすることができます。これにより、パブリックインターフェイスをより細かく制御できるようになり、一般的にForwardableを好む主な理由になります。 SimpleDelegator以上 。

Forwardableのもう1つの優れた機能 def_delegatorを介して委任されたメソッドの名前を変更する機能です 、目的のエイリアスを指定するオプションの3番目の引数を受け入れます:

class UserDecorator
  extend Forwardable
  def_delegator :@user, :first_name, :personal_name
  def_delegator :@user, :last_name, :family_name
 
  def initialize(user)
    @user = user
  end
 
  def full_name
    "#{personal_name} #{family_name}"
  end
end

上記のUserDecorator エイリアスされたpersonal_nameのみを公開します およびfamily_name first_nameに転送しながらメソッド およびlast_name ラップされたUserの オブジェクト:

decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.first_name
#=> NoMethodError: undefined method `first_name' for #<UserDecorator:0x000000010f995cb8>
decorated_user.personal_name
#=> "John"

この機能は、非常に便利な場合があります。私は過去に、類似したインターフェースを持つがメソッド名に関して異なる期待を持つライブラリ間でコードを移行するなどの目的でこれをうまく使用しました。

標準ライブラリの外部

標準ライブラリにある既存の委任ソリューションにもかかわらず、Rubyコミュニティは何年にもわたっていくつかの代替案を開発してきました。次に、そのうちの2つを検討します。

委任

Railsの人気を考えると、そのdelegate メソッドは、Ruby開発者が使用する最も一般的に使用される委任の形式である可能性があります。これを使用して、信頼できる古いUserDecoratorを書き換える方法は次のとおりです。 :

# In a real Rails app this would most likely be a subclass of ApplicationRecord
User = Struct.new(:first_name, :last_name)
 
class UserDecorator
  attr_reader :user
  delegate :first_name, :last_name, to: :user
 
  def initialize(user)
    @user = user
  end
 
  def full_name
    "#{first_name} #{last_name}"
  end
end
 
decorated_user = UserDecorator.new(User.new("John", "Doe"))
decorated_user.full_name
#=> "John Doe"

これはForwardableと非常によく似ています 、ただし、extendを使用する必要はありません delegate以降 Moduleで直接定義されています したがって、すべてのクラスまたはモジュール本体で使用できます(良くも悪くも、あなたが決定します)。ただし、delegate その袖にいくつかの巧妙なトリックがあります。まず、:prefixがあります 委任されたメソッド名の前に、委任先のオブジェクトの名前を付けるオプション。だから、

delegate :first_name, :last_name, to: :user, prefix: true

user_first_nameを生成します およびuser_last_name メソッド。または、カスタムプレフィックスを提供することもできます:

delegate :first_name, :last_name, to: :user, prefix: :account

これで、ユーザー名のさまざまな部分にaccount_first_nameとしてアクセスできます。 およびaccount_last_name

delegateのもう1つの興味深いオプション その:allow_nilです オプション。委任先のオブジェクトが現在nilの場合 -たとえば、ActiveRecordが設定されていないため 関係-通常はNoMethodErrorになります :

decorated_user = UserDecorator.new(nil)
decorated_user.first_name
#=> Module::DelegationError: UserDecorator#first_name delegated to @user.first_name, but @user is nil

ただし、:allow_nilでは オプションの場合、この呼び出しは成功し、nilを返します。 代わりに:

class UserDecorator
  delegate :first_name, :last_name, to: :user, allow_nil: true
 
  ...
end
 
decorated_user = UserDecorator.new(nil)
decorated_user.first_name
#=> nil

キャスト

最後に検討する委任オプションは、JimGayのCastingです。 gem。これにより、開発者は「Rubyでメソッドを委任し、自己を保持する」ことができます。これは、Rubyの動的な性質を使用して、これに似たメソッド呼び出しの受信者を一時的に再バインドするため、委任の厳密な定義におそらく最も近いものです。

UserDecorator.instance_method(:full_name).bind(user).call
#=> "John Doe"

これの最も興味深い側面は、開発者がスーパークラスの階層を変更せずにオブジェクトに動作を追加できることです。

require 'casting'
 
User = Struct.new(:first_name, :last_name)
 
module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end
end
 
user = User.new("John", "Doe")
user.extend(Casting::Client)
user.delegate(:full_name, UserDecorator)

ここでは、userを拡張しました Casting::Clientを使用 、delegateへのアクセスを提供します 方法。または、include Casting::Clientを使用することもできます。 User内 この機能をすべてのインスタンスに与えるクラス。

さらに、Casting ブロックの存続期間中、または手動で再度削除されるまで、動作を一時的に追加するためのオプションを提供します。これを機能させるには、まず、欠落しているメソッドの委任を有効にする必要があります。

user.delegate_missing_methods

単一のブロックの期間中の動作を追加するには、Castingを使用できます。 のdelegating クラスメソッド:

Casting.delegating(user => UserDecorator) do
  user.full_name #=> "John Doe"
end
 
user.full_name
#NoMethodError: undefined method `full_name' for #<struct User first_name="John", last_name="Doe">

または、uncastを明示的に呼び出すまで動作を追加することもできます もう一度:

user.cast_as(UserDecorator)
user.full_name
#=> "John Doe"
user.uncast
NoMethodError: undefined method `full_name' for #<struct User first_name="John", last_name="Doe">

提示された他のソリューションよりも少し複雑ですが、Casting は多くの制御を提供し、ジムは彼のCleanRubyの本でそのさまざまな使用法などを示しています。

概要

委任とメソッド転送は、関連するオブジェクト間で責任を分割するための便利なパターンです。プレーンなRubyプロジェクトでは、両方のDelegator およびForwardable Railsコードはdelegateに引き寄せられる傾向があるのに対し、使用できます。 方法。委任されるものを最大限に制御するには、Casting gemは優れた選択肢ですが、他のソリューションよりも少し複雑です。

ゲスト作家のマイケルコールのRubyとの恋愛関係は、2003年頃に始まりました。彼はまた、言語について書いたり話したりすることを楽しんでおり、Bangkok.rbとRubyConfThailandを共催しています。


  1. Rubyのカスタム例外

    Rubyで独自の例外を作成するのは簡単です。次の手順に従ってください: 1。新しいクラスを作成する 例外は、Rubyの他のすべてと同じように、クラスです。新しい種類の例外を作成するには、StandardErrorまたはその子の1つから継承するクラスを作成するだけです。 class MyError < StandardError end raise MyError 慣例により、新しい例外のクラス名は「エラー」で終わります。カスタム例外をモジュール内に配置することもお勧めします。つまり、最終的なエラークラスは次のようになります:ActiveRecord::RecordNotFound

  2. 知っておくべきWindows 11のヒントと隠された宝石

    Windows 11 は、互換性のあるデバイスの無料アップグレードとして利用でき、多くの新機能と改善が含まれています。新しく再設計されたスタート メニュー タスクバー、Andriod アプリをサポートする改良された Microsoft ストア、統合された Microsoft チーム、スナップ レイアウト、ウィジェットなどがあります。しかし、レドモンドの巨人によって公式に発表された大きなニュースと機能に加えて、Windows 11 には多くの小さな変更が含まれており、一見すると明らかではないかもしれません。この投稿では、Windows 11 の非表示の機能をいくつか紹介しました。 について知って