以前Forwardable
モジュールについて書いたとき、Rubyに標準ライブラリをもっと知っていた方がいいなというところを感じていました。
いくつか確認したので、メモ。
参考
試す Singleton Singleton
は Singleton
パターンを提供するモジュール。 クラスが単一のインスタンスしか持たなくなります。
test-singleton.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 require 'singleton' class SingletonClass include Singleton attr_reader :val def initialize @val = 1 end def set_val (val ) @val = val end def val @val end end sc1 = SingletonClass .instance sc2 = SingletonClass .instance puts sc1 puts sc2 puts sc2.val sc1.set_val(3 ) puts sc2.val
こういうことをすると、クラスメソッドとクラス変数でやってしまいそうなところですが、newでインスタンス化できないのがポイントになりそうでした。
Rubyによるデザインパターンまとめ という記事に注意が書いてあります。 この記事が「Rubyによるデザインパターン」という書籍のまとめ記事なのですが、書籍自体は絶版らしくアマゾンでは高額。 どうやら都内の図書館で借りられる様です。(都内で 9 館)
Rubyによるデザインパターン
この記事で触れられていることを正とすると、「状態」を持つことを避けるべきだそうです。 なので上記のような使い方はしていけないということです。
だとしたらほとんどmoduleでモジュール関数でいいんじゃないか?という感じ。
Observable Observer パターンを提供するモジュールObservable
。 注意なのは、Observable
を読み込むのは、Subjectであること。
test-observable-1.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 require "observer" class User include Observable attr_reader :name def initialize (name ) @name = name end def update_name (new_name ) unless @name == new_name @name = new_name changed notify_observers(self ) end end end class UserMailObserver def update (user ) puts "Mail: #{user.name} に変更されました" end end class UserChatObserver def update (user ) puts "Chat: #{user.name} に変更されました" end end u = User .new("user1" ) u.add_observer(UserMailObserver .new) u.add_observer(UserChatObserver .new) puts u.name u.update_name("user1-1" )
Userクラスのインスタンスの変更を、UserMailObserver
と UserChatObserver
が受け取り、それぞれのupdateメソッドを実行する。 変更を検知し実行する内容が増えても、add_observer
でオブザーバーを追加することで対応できる。
もし、オブザーバーの処理が例外を出したらどうなるか確認する。
test-observable-2.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 require "observer" class User include Observable attr_reader :name def initialize (name ) @name = name end def update_name (new_name ) unless @name == new_name @name = new_name changed notify_observers(self ) end end end class UserMailObserver def update (user ) puts "Mail: #{user.name} に変更されました" end end class UserChatObserver def update (user ) puts "Chat: #{user.name} に変更されました" end end class UserRaiseObserver def update (user ) raise end end u2 = User .new("user2" ) u2.add_observer(UserMailObserver .new) u2.add_observer(UserRaiseObserver .new) u2.add_observer(UserChatObserver .new) u2.update_name("user2-2" )
結論として、例外を出した後の UserChatObserver は呼び出されなかった。 Observer 側で例外が閉じていることが望ましいのでしょうが、敢えて例外の可能性を踏まえて Subject で対応してみます。
test-observable-3.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 require "observer" class UserPossibilityRaise include Observable attr_reader :name def initialize (name ) @name = name end def update_name (new_name ) unless @name == new_name @name = new_name changed begin notify_observers(self ) rescue => e puts e changed(false ) end end end end class UserMailObserver def update (user ) puts "Mail: #{user.name} に変更されました" end end class UserChatObserver def update (user ) puts "Chat: #{user.name} に変更されました" end end class UserRaiseObserver def update (user ) raise "Error : UserRaiseObserver#update" end end u3 = UserPossibilityRaise .new("user3" ) u3.add_observer(UserMailObserver .new) u3.add_observer(UserRaiseObserver .new) u3.add_observer(UserChatObserver .new) u3.update_name("user3-3" )
例外は処理できますが、後続のものを再実行するであるとかはできないので、あくまでオブザーバー側で処理した方が良さそうです。 再実行や必須でまとまった処理だとを考慮する部分は、単一のオブザーバー側の中で閉じさせ処理を書き連ねるのがよさそうです。
SingleForwardable Forwardable モジュールでのメソッド移譲 でForwardable
ライブラリを使いました。Forwardable
ライブラリは、Forwardable
モジュールだけでなく、SingleForwardable
を提供していました。
SingleForwardable
の使い方を確認しました。SingleForwardable
は、オブジェクトへのメソッドの移譲機能を提供します。
test-singleforwardable.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 require 'forwardable' class Aa extend Forwardable attr_reader :arr def_delegator : @arr , :each def initialize (arr ) @arr = arr end end a = Aa .new([1 ,2 ,3 ]) a.each{ |p | puts p} class Bb extend SingleForwardable @@arr = [1 ,2 ,3 ] def_delegator @@arr , :each end Bb .each{ |p | puts p}module BuiltInCc def self .call "BuiltInCc#call" end end module Cc extend SingleForwardable def_delegator BuiltInCc , :call end puts Cc .call module BuiltInDd def self .call "BuiltInDd#call" end end class Dd extend Forwardable extend SingleForwardable attr_reader :arr def_single_delegator BuiltInDd , :call def_instance_delegator : @arr , :each def_instance_delegator : @arr , :<< def initialize (arr ) @arr = arr end end puts Dd .call d = Dd .new([1 ,2 ,3 ,4 ]) d.each{ |p | puts p} d << 999 d.each{ |p | puts p}
うまく使わないと大混乱を生みそう。
PStore Ruby のオブジェクトをファイルに書き出すことができる。
test-pstore.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 require 'pp' require 'pstore' require 'ostruct' database = PStore .new("./obj" ) obj = OpenStruct .new({a: [1 ,2 ,3 ], b: "BBB" }) database.transaction do database["root" ] = obj end database.transaction(true ) do pp database p database.roots pp database["root" ].a end database.transaction(true ) do |db | pp db.fetch("root" ) pp db.fetch("root" , "defult" ) pp db.fetch("root_1" , "defult" ) end database.transaction do |db | pp db.delete("root" ) pp db.fetch("root" , "defult" ) end
保存されているのはオブジェクト単体ではなく、複数のオブジェクトがtableプロパティに含まれたものでした。 そういう意味でデータベースですね。
書き込みされたオブジェクトは、以下のように記述されていました。 読めることを期待していたけど、それはできないものでした。
./obj 1 {I" root:ETU:OpenStruct{:a[iii:bI"BBB;
デザインパターンにかかわるモジュールも触ってみました。 うまく活用したいところです。
ではでは。