From: quixoticsycophant@... (Jeff Mitchell) Date: 2003-09-02T05:29:03+09:00 Subject: encapsulating instance data Happy Holidays! It always concerned me that instance variables of a class propagate to its subclasses and that instance variables of a module propagate to classes which include it. This lack of private variables caused me great unrest until I learned a little more ruby and then realized private variables can be implemented in ruby itself! What do you think? Criticism is welcome; this one of my first non-hello-world ruby scripts so be nice :) And thanks to the irc folks who patiently answered my questions. #---------------------------------- # synopsis #---------------------------------- require 'wrap' module A def a_f @x = 11 end def a_x @x end end module B def b_f @x = 22 end def b_x @x end end class C include Wrap(A) include Wrap(B) end c = C.new c.a_f c.b_f puts "A's @x is #{c.a_x}" puts "B's @x is #{c.b_x}" # => A's @x is 11 # => B's @x is 22 class P def initialize @x = 33 end def p_x @x end end class Q < Wrap P def initialize super() @x = 44 end def q_x @x end end q = Q.new puts "P's @x is #{q.p_x}" puts "Q's @x is #{q.q_x}" # => P's @x is 33 # => Q's @x is 44 #---------------------------------- # wrap.rb # # privatize data using delegation to an anonymous class. #---------------------------------- def Wrap mod if mod.class == Module wrap_module(mod) elsif mod.class == Class wrap_class(mod) else raise "expected Class or Module for Wrap: got #{mod.class}" end end def wrap_module mod wrap = Module.new classname = "Private__class#{wrap.object_id}" instance = "@__private#{wrap.object_id}" wrap.const_set(classname.intern, Class.new{ include mod }) mod.constants.each do |name| wrap.const_set(name.intern, mod.const_get(name.intern)) end create = Proc.new do |visibility, method| next if method =~ /^__/ wrap.class_eval <<-end_eval def #{method}(*args, &block) #{instance} ||= #{classname}.new #{instance}.__send__(:#{method}, *args, &block) end end_eval wrap.__send__(visibility, method) end mod.public_instance_methods(true).each{|m|create.call(:public,m)} mod.protected_instance_methods(true).each{|m|create.call(:protected,m)} mod.private_instance_methods(true).each{|m|create.call(:protected,m)} wrap end def wrap_class aclass wrap = Class.new instance = "@__private#{wrap.object_id}" aclass.constants.each do |name| wrap.const_set(name.intern, aclass.const_get(name.intern)) end aclass.singleton_methods(true).each do |method| wrap.class_eval <<-end_eval def self.#{method}(*args, &block) #{aclass.name}.#{method}(*args, &block) end end_eval end wrap.class_eval <<-end_eval def initialize(*args, &block) #{instance} = #{aclass.name}.new(*args, &block) end end_eval create = Proc.new do |visibility, method| next if method == "initialize" next if method =~ /^__/ wrap.class_eval <<-end_eval def #{method}(*args, &block) #{instance}.__send__(:#{method}, *args, &block) end end_eval wrap.__send__(visibility, method) end aclass.ancestors.each do |c| break if c == Object c.public_instance_methods(false).each{|m|create.call(:public,m)} c.protected_instance_methods(false).each{|m|create.call(:protected,m)} c.private_instance_methods(false).each{|m|create.call(:private,m)} end wrap end #---------------------------------- It's both like and unlike delegation. With delegation, you pass an object explicitly and the dual-object relationship is part of the design. With wrapping there is no object explicitly passed and the delegation is hidden; there are still two objects but you pretend one of them doesn't exist. But essentially it's just a notational convenience for a certain form of delegation. I see some specialized things would make it faster, for example direct evals of public methods (no __send__) and re-evaling after the first method call to remove the nil test for module methods. Anyway, all of this may be too silly. It's really an anecdote of sorts. I was surprised that ruby was flexible enough that I could seemingly add (or simulate) a new language construct from inside the language itself. -Jeff (quix)