* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / lib / delegate.rb
blob629753fe3a7474f3c5e76d8e4a7103a96b212188
1 # = delegate -- Support for the Delegation Pattern
3 # Documentation by James Edward Gray II and Gavin Sinclair
5 # == Introduction
7 # This library provides three different ways to delegate method calls to an
8 # object.  The easiest to use is SimpleDelegator.  Pass an object to the
9 # constructor and all methods supported by the object will be delegated.  This
10 # object can be changed later.
12 # Going a step further, the top level DelegateClass method allows you to easily
13 # setup delegation through class inheritance.  This is considerably more
14 # flexible and thus probably the most common use for this library.
16 # Finally, if you need full control over the delegation scheme, you can inherit
17 # from the abstract class Delegator and customize as needed.  (If you find
18 # yourself needing this control, have a look at _forwardable_, also in the
19 # standard library.  It may suit your needs better.)
21 # == Notes
23 # Be advised, RDoc will not detect delegated methods.
25 # <b>delegate.rb provides full-class delegation via the
26 # DelegateClass() method.  For single-method delegation via
27 # def_delegator(), see forwardable.rb.</b>
29 # == Examples
31 # === SimpleDelegator
33 # Here's a simple example that takes advantage of the fact that
34 # SimpleDelegator's delegation object can be changed at any time.
36 #   class Stats
37 #     def initialize
38 #       @source = SimpleDelegator.new([])
39 #     end
40 #     
41 #     def stats( records )
42 #       @source.__setobj__(records)
43 #               
44 #       "Elements:  #{@source.size}\n" +
45 #       " Non-Nil:  #{@source.compact.size}\n" +
46 #       "  Unique:  #{@source.uniq.size}\n"
47 #     end
48 #   end
49 #   
50 #   s = Stats.new
51 #   puts s.stats(%w{James Edward Gray II})
52 #   puts
53 #   puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
55 # <i>Prints:</i>
57 #   Elements:  4
58 #    Non-Nil:  4
59 #     Unique:  4
60
61 #   Elements:  8
62 #    Non-Nil:  7
63 #     Unique:  6
65 # === DelegateClass()
67 # Here's a sample of use from <i>tempfile.rb</i>.
69 # A _Tempfile_ object is really just a _File_ object with a few special rules
70 # about storage location and/or when the File should be deleted.  That makes for
71 # an almost textbook perfect example of how to use delegation.
73 #   class Tempfile < DelegateClass(File)
74 #     # constant and class member data initialization...
75 #   
76 #     def initialize(basename, tmpdir=Dir::tmpdir)
77 #       # build up file path/name in var tmpname...
78 #     
79 #       @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
80 #     
81 #       # ...
82 #     
83 #       super(@tmpfile)
84 #     
85 #       # below this point, all methods of File are supported...
86 #     end
87 #   
88 #     # ...
89 #   end
91 # === Delegator
93 # SimpleDelegator's implementation serves as a nice example here.
95 #    class SimpleDelegator < Delegator
96 #      def initialize(obj)
97 #        super             # pass obj to Delegator constructor, required
98 #        @delegate_sd_obj = obj    # store obj for future use
99 #      end
101 #      def __getobj__
102 #        @delegate_sd_obj          # return object we are delegating to, required
103 #      end
105 #      def __setobj__(obj)
106 #        @delegate_sd_obj = obj    # change delegation object, a feature we're providing
107 #      end
109 #      # ...
110 #    end
113 # Delegator is an abstract class used to build delegator pattern objects from
114 # subclasses.  Subclasses should redefine \_\_getobj\_\_.  For a concrete
115 # implementation, see SimpleDelegator.
117 class Delegator
118   preserved = [
119     :__id__, :object_id, :__send__, :public_send, :respond_to?, :send,
120     :instance_eval, :instance_exec, :extend,
121   ]
122   instance_methods.each do |m|
123     next if preserved.include?(m)
124     undef_method m
125   end
127   module MethodDelegation
128     #
129     # Pass in the _obj_ to delegate method calls to.  All methods supported by
130     # _obj_ will be delegated to.
131     #
132     def initialize(obj)
133       __setobj__(obj)
134     end
136     # Handles the magic of delegation through \_\_getobj\_\_.
137     def method_missing(m, *args, &block)
138       begin
139         target = self.__getobj__
140         unless target.respond_to?(m)
141           super(m, *args, &block)
142         else
143           target.__send__(m, *args, &block)
144         end
145       rescue Exception
146         $@.delete_if{|s| %r"\A#{__FILE__}:\d+:in `method_missing'\z"o =~ s}
147         ::Kernel::raise
148       end
149     end
151     # 
152     # Checks for a method provided by this the delegate object by fowarding the 
153     # call through \_\_getobj\_\_.
154     # 
155     def respond_to?(m, include_private = false)
156       return true if super
157       return self.__getobj__.respond_to?(m, include_private)
158     end
160     # 
161     # Returns true if two objects are considered same.
162     # 
163     def ==(obj)
164       return true if obj.equal?(self)
165       self.__getobj__ == obj
166     end
168     # 
169     # Returns true only if two objects are identical.
170     # 
171     def equal?(obj)
172       self.object_id == obj.object_id
173     end
175     #
176     # This method must be overridden by subclasses and should return the object
177     # method calls are being delegated to.
178     #
179     def __getobj__
180       raise NotImplementedError, "need to define `__getobj__'"
181     end
183     #
184     # This method must be overridden by subclasses and change the object delegate
185     # to _obj_.
186     #
187     def __setobj__(obj)
188       raise NotImplementedError, "need to define `__setobj__'"
189     end
191     # Serialization support for the object returned by \_\_getobj\_\_.
192     def marshal_dump
193       __getobj__
194     end
195     # Reinitializes delegation from a serialized object.
196     def marshal_load(obj)
197       __setobj__(obj)
198     end
200     # Clone support for the object returned by \_\_getobj\_\_.
201     def clone
202       new = super
203       new.__setobj__(__getobj__.clone)
204       new
205     end
206     # Duplication support for the object returned by \_\_getobj\_\_.
207     def dup
208       new = super
209       new.__setobj__(__getobj__.dup)
210       new
211     end
212   end
213   include MethodDelegation
217 # A concrete implementation of Delegator, this class provides the means to
218 # delegate all supported method calls to the object passed into the constructor
219 # and even to change the object being delegated to at a later time with
220 # \_\_setobj\_\_ .
222 class SimpleDelegator<Delegator
223   # Returns the current object method calls are being delegated to.
224   def __getobj__
225     @delegate_sd_obj
226   end
228   #
229   # Changes the delegate object to _obj_.
230   #
231   # It's important to note that this does *not* cause SimpleDelegator's methods
232   # to change.  Because of this, you probably only want to change delegation
233   # to objects of the same type as the original delegate.
234   #
235   # Here's an example of changing the delegation object.
236   #
237   #   names = SimpleDelegator.new(%w{James Edward Gray II})
238   #   puts names[1]    # => Edward
239   #   names.__setobj__(%w{Gavin Sinclair})
240   #   puts names[1]    # => Sinclair
241   #
242   def __setobj__(obj)
243     raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
244     @delegate_sd_obj = obj
245   end
248 # :stopdoc:
249 def Delegator.delegating_block(mid)
250   lambda do |*args, &block|
251     begin
252       @delegate_dc_obj.__send__(mid, *args, &block)
253     rescue
254       re = /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o
255       $!.backtrace.delete_if {|t| re =~ t}
256       raise
257     end
258   end
260 # :startdoc:
263 # The primary interface to this library.  Use to setup delegation when defining
264 # your class.
266 #   class MyClass < DelegateClass( ClassToDelegateTo )    # Step 1
267 #     def initialize
268 #       super(obj_of_ClassToDelegateTo)                   # Step 2
269 #     end
270 #   end
272 def DelegateClass(superclass)
273   klass = Class.new
274   methods = superclass.public_instance_methods(true)
275   methods -= [
276     :__id__, :object_id, :__send__, :public_send, :respond_to?, :send,
277     :==, :equal?, :initialize, :method_missing, :__getobj__, :__setobj__,
278     :clone, :dup, :marshal_dump, :marshal_load, :instance_eval, :instance_exec,
279     :extend,
280   ]
281   klass.module_eval {
282     include Delegator::MethodDelegation
283     def __getobj__  # :nodoc:
284       @delegate_dc_obj
285     end
286     def __setobj__(obj)  # :nodoc:
287       raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
288       @delegate_dc_obj = obj
289     end
290   }
291   klass.module_eval do
292     methods.each do |method|
293       define_method(method, Delegator.delegating_block(method))
294     end
295   end
296   return klass
299 # :enddoc:
301 if __FILE__ == $0
302   class ExtArray<DelegateClass(Array)
303     def initialize()
304       super([])
305     end
306   end
308   ary = ExtArray.new
309   p ary.class
310   ary.push 25
311   p ary
312   ary.push 42
313   ary.each {|x| p x}
315   foo = Object.new
316   def foo.test
317     25
318   end
319   def foo.iter
320     yield self
321   end
322   def foo.error
323     raise 'this is OK'
324   end
325   foo2 = SimpleDelegator.new(foo)
326   p foo2
327   foo2.instance_eval{print "foo\n"}
328   p foo.test == foo2.test       # => true
329   p foo2.iter{[55,true]}        # => true
330   foo2.error                    # raise error!