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を共催しています。
-
Rubyのカスタム例外
Rubyで独自の例外を作成するのは簡単です。次の手順に従ってください: 1。新しいクラスを作成する 例外は、Rubyの他のすべてと同じように、クラスです。新しい種類の例外を作成するには、StandardErrorまたはその子の1つから継承するクラスを作成するだけです。 class MyError < StandardError end raise MyError 慣例により、新しい例外のクラス名は「エラー」で終わります。カスタム例外をモジュール内に配置することもお勧めします。つまり、最終的なエラークラスは次のようになります:ActiveRecord::RecordNotFound
-
知っておくべきWindows 11のヒントと隠された宝石
Windows 11 は、互換性のあるデバイスの無料アップグレードとして利用でき、多くの新機能と改善が含まれています。新しく再設計されたスタート メニュー タスクバー、Andriod アプリをサポートする改良された Microsoft ストア、統合された Microsoft チーム、スナップ レイアウト、ウィジェットなどがあります。しかし、レドモンドの巨人によって公式に発表された大きなニュースと機能に加えて、Windows 11 には多くの小さな変更が含まれており、一見すると明らかではないかもしれません。この投稿では、Windows 11 の非表示の機能をいくつか紹介しました。 について知って