The following example implements the State design pattern (trivially). Consider a system that implements a sequence of integers as a class. Calls to its next method return subsequent values from the sequence. The sequence also allows its state to be saved and restored, via an opaque SequenceState object, thereby allowing the sequence to be restarted from some previous value.
Here is where the selective export is useful. The SequenceState object should be opaque to everyone except the Sequence itself (or to other classes that want to mimic the behavior of a Sequence). The Sequence object must be able to query and modify the values in the SequenceState object that represent the Sequence's internal state.
Here is the implementation of the SequenceState class:
class SequenceState
def get_counter
@counter
end
def set_counter( counter )
@counter = counter
end
publish :StateAccess, :get_counter, :set_counter
end
Notice the last line; this publishes a new profile, called "StateAccess", and indicates that both "get_counter" and "set_counter" should be in this profile. Putting those methods in the profile also implicitly makes those methods private.
The Sequence class is implemented thus:
class Sequence
def initialize( start=0 )
@counter = start-1
end
def next
@counter += 1
@counter
end
def get_state
state = SequenceState.new.subscribe( :StateAccess ) do |access|
access.set_counter @counter
end
return state
end
def restore_state( state )
state.subscribe( :StateAccess ) do |access|
@counter = access.get_counter
end
end
end
Notice the "get_state" and "restore_state" methods. In "get_state", a SequenceState object is instantiated. Then, the caller subscribes to the StateAccess profile of the new object. A proxy object, which has access to methods of that profile (including "set_counter"), is then passed to the corresponding block.
In restore_state, the caller subscribes to the StateAccess profile of the state object, which gives them access to the "get_counter" method of the state, thus allowing them to restore the value of the counter.
The subscription process and use of a proxy object do incur performance penalties, which should be understood by developers that wish to use this feature. However, the performance hit on calling through a proxy is not too bad (about 2-3 times slower than a direct method invocation). The subscription hit is more significant (about 20x slower than doing without a subscription). The following table shows the results of some benchmarks. Each test involved 1,000,000 method invocations.
Direct: 1.024s Subscribed: 2.769s 1,000,000 subscriptions: 24.26s
In an attempt to improve performance, I wrote a C version of the pubsub module. Although it did not improve the performance of the call through the proxy significantly (2.5s for 1,000,000 method invocations), it did improve the subscription performance (7.5s for 1,000,000 subscriptions).
However, it should be stated that use of this feature is intended for those relatively infrequent occassions when an object needs access to the internal state of another object. In general, this shouldn't happen in tight iterations, where speed is necessary, and so the lower performance of this feature should not be a concern.
It may be argued that this kind of visibility limitation is no limitation at all, since any object at all may subscribe to a service, if it knows about the service. However, the point (in this case) is not to limit visibility. Designers still have access to private and protected access modes, which prevent outside access to methods so annotated. Selective export is not about preventing access, but controlling access. It ensures that the clients must know the proper "protocol" for communicating with an object.
Given that the reference implementation is functional, it may be asked why this RCR is necessary (since the same functionality is now available as a module). There are two reasons:
The module referenced above is implemented by reopening Object and adding functionality to it, thereby granting both publish and subscribe to every class. It might also be implemented as a module which is included by those classes that need it; this approach might be more desirable if the Object class needs to be kept "lean".
Back to RCRchive.
RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog