Submitted by minam (Sat Jan 10 00:01:33 UTC 2004)
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 <i>except the Sequence itself</i> (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 profile :StateAccess, :get_counter, :set_counter end
Notice the last line; this declares a new profile, called "StateAccess", and indicates that both "get_counter" and "set_counter" should be in this profile. <i>Putting those methods in the profile also implicitly makes those methods private.</i>
The Sequence class is implemented thus:
class Sequence def initialize( start=0 ) @counter = start-1 end def next @counter += 1 end def get_state SequenceState.new.subscribe( :StateAccess ) do |state| state.set_counter @counter return state end end def restore_state( state ) state.subscribe( :StateAccess ) do @counter = state.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, which gives the corresponding block access to the "set_counter" method of the state object.
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.
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 <i>controlling</i> access. It ensures that the clients must know the proper "protocol" for communicating with an object.
The reference implementation given in the following section has an interesting side effect, which may or may not be desirable (further discussion should help to clarify this). The side effect is this: any subscription causes all associated methods to be public during the life of the associated block. The implications of this are twofold:
<li>If the publishing object is passed to another method from within the block, all of the subscribed methods will be publicly accessible within the called method.</li> <li>If multiple threads are accessing the publishing object simultaneously, and one of them subscribes to selectively exported methods, the other thread will have access to those methods as well.</li>
In general, I suspect the first side effect is desirable, whereas the second is not. It should also be noted that eliminating the first side-effect would probably result in eliminating the second.
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:
<li>Although it may be implemented completely in Ruby, there are limitations to such an implementation. To avoid undesirable side-effects (like inadvertantly granting a parallel thread access to published methods), it may be necessary to implement part, if not all, of the functionality of this in C--perhaps even making minor modifications to the interpreter itself.</li> <li>Making such functionality a standard part of the Ruby distribution--whether it is implemented purely in Ruby or not--would encourage developers to make use of it. If it must be distributed with every project that uses it, or if it must be installed before projects that use it may be installed, it makes it more difficult for it to be properly adopted.</li>
The module referenced above is implemented by reopening Object and adding functionality to it, thereby granting both profile
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".
Comments | Current voting | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
This RCR has been superseded by RCR 198.
RCRchive copyright © David Alan Black, 2003-2005.
Powered by .
Somewhat in opposition of the feature. Not because I don't like it, but because I think it should be more encompassing. The publish visibility needs to extend to aliases, modules and classes, too. And it needs to also consider redefinitions of methods (so as to allow publishing or not changes to say, a base class). Also, it needs to support a non-block syntax so as to encompass a similar functionality similar to David Black's behaviors but in both a thread safe manner and with a more succint syntax.
Using a proxy for access
I think "publish" is a better class method name than "profile", as it is easily associated with the publish/subscribe mechanism, and is a better fit with the existing method visibility modifiers (public, protected, private).
Overall naming suggestion:
Another thing that might be an improvement is to access the published methods via a PublishSubscribeProxy object that is accessed by an instance method having the same name as the profile defined by "publish". The call then looks like "some_instance.AProfile.a_method(42)", as in the following example:
I think that this way of accessing published methods is clearer, because you have to state the profile thru which you want to access the published methods.
Note that the instance method used to access the proxy object (called using "sc.AProfile" in the above example) is private, but becomes public when the profile is subscribed to. In other words, the proxy object is hidden (private) until subscribed to.
Having the call done via this syntax seems to make those mentioned side-effects less of an issue, because the associated methods are not exposed directly from the object, but are instead exposed (when subscribed to) by the proxy object. The side-effects are still there, but the exposed methods need to be accessed via the proxy object by using the name of the profile in the method call.
Example for use of the added introspection methods (continuing from above examples):
Sorry to post a lot of code here (below)... maybe Jamis Buck might add what follows to the pubsub project on RAA?
I've created a "pubsub2" reference implementation (derived from pubsub) that implements the abovementioned changes:
Here's some test code that I ran on it:
Cheers,
- corey.