encapsulating instance data

J

Jeff Mitchell

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)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,121
Messages
2,570,714
Members
47,282
Latest member
hopkins1988

Latest Threads

Top