From: Robert Klemme Date: 2005-01-26T18:50:54+09:00 Subject: Re: Self and Ruby Comparisons ------=_NextPart_000_0024_01C50394.DB6687A0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit "Trans" schrieb im Newsbeitrag news:1106680107.836198.191680@f14g2000cwb.googlegroups.com... > robert, > > Quite right, I'm not sure Ruby is particularly suited for role > swapping. (Prototype-based OOP handles it gracefully) And I don't know > how inefficent #become would be. It would be nice to think its just a > matter of changing one pointer, but I know that's not the case. > > Your role delegator is nice. At the very least it's much easier to be > more than one thing at a time. (There's no problem with roles > interfereing with each at the method name level is there?) No, as each role is handled by it's own delegator instance. The downside is that roles cannot interact directly but need to go via role: module Role1 def work() puts "r1: #{name}" end end module Role2 def work() puts "r2: #{name}"; role(Role1).work() end end class Person attr_accessor :name end >> pp = Person.new => # >> pp.name = "Gavagai" => "Gavagai" >> pp.role(Role1).work() r1: Gavagai => nil >> pp.role(Role2).work() r2: Gavagai r1: Gavagai => nil > Unfortuately > I suspect that it may be nearly as inefficient as repeated using > #become (but that's just a guess). Not necessarily as long as you don't continuously use unrole() - because delegator instances are stored in a hash and thus are reused. So a role can have it's own state - separated from the main instance's state which is a good thing when it comes to prevention of name clashes. The only other inefficiency stems from delegation (in the example above method name() is delegated). The more I think about using delegation for this the more I like it because it has some nice properties: no problems with name clashes in different modules, every role has it's own state that is dropped together with the role etc. I'll attach a slightly reworked version. > Thanks for the illustration, I think I'll store this for potential > future use/exploration. You're welcome. > BTW, does #unrole actually work? Yes, but as soon as you do role() again, the role is created again. You can see only via role?() whether a role is adopted or not. Of course one could change the pattern to have a separate add_role() then you will get an error if you use role() but the instance does not play the role - might be better in terms of robustness. Kind regards robert ------=_NextPart_000_0024_01C50394.DB6687A0 Content-Type: application/octet-stream; name="roles.rb" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="roles.rb" require 'delegate'=0A= =0A= class Object=0A= def play_roles(*mod)=0A= mod.map {|m| play_role(m)}=0A= end=0A= =0A= def play_role(mod)=0A= (@roles ||=3D {})[mod] ||=3D SimpleDelegator.new(self).extend(mod)=0A= end=0A= =0A= def role(mod)=0A= r =3D @roles && @roles[mod]=0A= raise TypeError, "Role not played: #{mod}" unless r=0A= yield r if block_given?=0A= r=0A= end=0A= =0A= def role?(mod)=0A= @roles and @roles.has_key? mod=0A= end=0A= =0A= def end_role(mod)=0A= @roles and @roles.delete(mod)=0A= end=0A= =0A= def roles=0A= @roles ? @roles.keys : []=0A= end=0A= end=0A= =0A= class Foo=0A= attr_accessor :name=0A= end=0A= =0A= module Bar=0A= def xx() name() end=0A= end=0A= =0A= module Bax=0A= def aa() name() end=0A= end=0A= =0A= f =3D Foo.new=0A= f.name =3D "George"=0A= puts f.role? Bar=0A= f.play_role Bar=0A= puts f.role? Bar=0A= p f.role(Bar).xx()=0A= =0A= f.role(Bar) do |r|=0A= p r.xx()=0A= p r.__getobj__=0A= end=0A= =0A= p f.play_roles Bar, Bax=0A= =0A= puts f.role? Bar=0A= p f.roles=0A= f.end_role(Bar)=0A= p f.roles=0A= puts f.role? Bar=0A= =0A= ------=_NextPart_000_0024_01C50394.DB6687A0--