Borg Pattern Class usable from other classes?

Alex Martelli aleax at aleax.it
Sat Oct 13 04:53:26 EDT 2001


Paul Winkler wrote:

> On Fri, 12 Oct 2001 10:27:23 +0200, Alex Martelli <aleax at aleax.it> wrote:
>>Highlander for the DP, and Borg for the non-pattern which I
>>posted and D. Ascher named, seem much more appropriate names,
>>as the connection to science fiction movies/TV series is sharp
>>and pleasingly anti-parallel.
> 
> AFAIKT, Borg is the same as Monostate which seems to have been around
> for a couple years:
> 
> http://www.ddj.com/articles/1998/9809/9809a/9809as2.htm
> 
> At least, the Consequences are identical:
> """
>     * Multiple local instantiations but always with only a single state.
>     * Persistent object state in the absence of any instantiations.
>     * Familiar syntax. Does not require an accessor function as with
>       Singleton objects.
> """
> 
> Borg is an OK name but Monostate is a little more obvious to those of
> us who don't know much about Star Trek (yes, we exist).

Borg and Monostate (attempt to) solve the same forces.  However, they do so 
in different languages.  *Language-appropriateness* of patterns is often 
ignored, even though the Gang of 4 mention it quite well right in Chapter 1,
exemplifying with Visitor, which would be pretty silly in a language with 
multiple dispatch.  Similarly, "just keep all data static" (which I had 
first met, without a catchy name, in the context of C++) is OK for C++ or 
Java, but not for Python, where you may want to allow, for example:
        theobj.acount += 1

This would be bad practice in languages bereft of "property idioms" such as 
C++ of Java, but it's fine where properties are indeed idiomatic, such as 
Delphi or Python: the class author can and should ensure that accesses to 
properties "from the outside" go through appropriate method calls 
implicitly if need be.  It would be anti-idiomatic to force client code 
through such contortions as
        theobj.setAcount(theobj.getAcount() + 1)
in such languages (even though people who don't fully grasp a language do 
try to "program Java in Delphi" and so on, of course, good idiomatic 
language use has substantial advantages).

The "keep it static" (Monostate) pattern in Python would give:

class AClass:
        acount = 0

and would work fine as long as you're just accessing x.acount from client 
code (AClass.acount would implicitly be used, just as in a similar 
situation in C++ or Java), but not for rebinding (or deletion).  You'd have 
to impose supplementary, non-Pythonic restriction on client code, or start 
messing with __setattr__ &c (in Python 2.2, you could use less-messy
specific per-attribute getters/setters, but if you have more attributes 
it's still rather a lot of boilerplate).


> BTW, are there any "gotchas" with Borg/Monostate in a multi-threaded
> context? Singleton requires extra care, leading to the "Double-checked
> Locking" pattern which apparently has been found not to work in
> Java. Any such snags for Borg? I can't think of any, but I've only
> really toyed with threads.

If two Borg instances are created in separate threads, they still access 
the same state-data, so you get the joys of dealing with whatever 
synchronization issues.  There's no implicit per-object or per-method 
locking in Python anyway (unless you make it yourself with a metaclass, I 
guess, but I've never tried doing that).  No extra care wrt direct 
accessing shared state, but no special help for that, either.

Although it may be a bit heavyweight for many uses, there is a Pythonic 
pattern based on Queue for multi-thread access to shared state, which I 
haven't seen published as such but I think could be called GateKeeper or
Guardian.  A guardian-thread is sole accessor of a complex of shared state 
(which could but need not be a Borg).  All access to the guardian thread is 
by posting to a specific Queue tuples of (ResponseQueue, boundmethod, args, 
kwds) followed (at once or a bit later) by waiting on the ResponseQueue.  

The Guardian thread just loops taking request pairs from its Queue, running 
the boundmethod, posting the result to the ResponseQueue.  Yep, it's 
overkill in many cases, but it does lend itself to neat encapsulation.

Say the client code only "sees" object guardianProxy which wraps 
sharedObject (which could but need not be a Borg).  Then client code just 
calls, e.g.:
        myresult = guardianProxy.someMethod(someThing,anOther)
and the only issues for client code are with "granularity of locks" -- i.e. 
if client code tries
        mytotal = guardianProxy.oneThing()+guardianProxy.anOther()
anything may happen between the two method calls although each is running 
atomically (needs some explicit locking if that is a problem!).

GuardianProxy's __getattr__ delegates to sharedObject's, but, when it gets 
a callable, builds on the fly another callable wrapping it, and returns the 
wrapper.  The wrapper callable does the queue-minuet on behalf of its 
caller:

class QueueWrapper:
    def __init__(self, realthing, requestQueue):
        self.realthing = realthing
        self.requestQueue = requestQueue
    def __call__(self, *args, **kwds):
         responseQueue = Queue()
         self.requestQueue.put((
             responseQueue,self.realthing,args,kwds))
         return responseQueue.get()

The overkill aspects come e.g. in the overgenerous creation of Queue 
objects, but it's not hard to remedy by adding some usage conventions 
regarding what reuse (or lack thereof) is made of the QueueWrapper 
callables.  It's just a bit harder to extend this to self.realthing raising 
an exception in the guardian thread to be propagated this back to the 
client thread: the responseQueue would have to receive exception-indicators 
and re-raise appropriately.

But more often, I think non-hypergeneralized Queue usages that in some 
sense specialcase this ambitious general scheme are typical and 
appropriate.  But the whole issue of non-Patterns vs Patterns is a rather 
large subject -- we can hardly do it justice here.


Alex




More information about the Python-list mailing list