Singleton vs Proxies DP (was Re: Solution: Direct access to Printer I/O lines)

Alex Martelli aleaxit at yahoo.com
Sat Dec 16 21:47:02 CET 2000


"Carel Fellinger" <cfelling at iae.nl> wrote in message
news:91gfnp$7r4$1 at animus.fel.iae.nl...
> Alex Martelli <aleaxit at yahoo.com> wrote:
> ...
> > This gives me a good chance for one of my favourite rants,
> > '"Singleton" design-pattern considered harmful'!-)  But
> > 'Singleton' is not being ideally applied, so I'll skip that.
>
> And in an other thread you wet my appetite and direct me here!
> So let us hear that rant, mister, and enlight us mere mortals
> who still think that there is legitimate use of this singleton
> pattern.

It's not rare to desire that a certain resource (e.g., a RDBMS
running on another process/machine) be handled through one,
and one only, 'bottleneck'.  Singleton does ensure that, yes.
There is a space for singletons.  But the Singleton _Design
Pattern_ need not be the best way to have singletons.

Within the component that implements the singleton, it
does not matter how and where you place the state, and
the code that knows about that state's representation and
accesses and modifies it directly.  You may choose to
have that state as instance-variables of an object, that
code in its methods, and ensure a single instantiation of
that object, but that's a somewhat roundabout way for
languages which allow per-class (C++) or per-module
(Python) functions and data.  But it's a minor decision, of
implementation more than of design; what matters is the
interface you choose to expose from that component.

And _should_ that interface be one of a single object that
gets instantiated once, so it must be accessed through
some single 'give me a reference to Mr. Singleton please'
function?  I do not see why that could ever be the ideal
interface.  There are all sorts of issues that give slight
problems, and I don't see any compensating benefit when
compared with the following approach.

Why not have client-code ask for objects normally,
use them, then drop them when it's done?  If client
code asks for 'another object' -- why not hand it out?

It doesn't matter if the 'handed out' objects have
different *identities*... as long as they share *state
and behavior*, no?  I forget on what name this simple
pattern was originally published in "C++ Report",
but let me call it 'lightweight proxies' (LWP, versus S
for the singleton pattern).  It's just one of many, many
examples of the benefits that can often be had by
splitting notions of id and state in OO programming.

An example of benefit in Python: say that LWP is the
class in the library module that implements the
lightweight proxies pattern.  It exposes functionality
and state for some underlying in-one-instance-only
object S.  That functionality may not be in the best
shape for client-code, that may want to adapt it --
maybe add or tweak some methods from the rest of
the client code in a 'middleware' stratum.

class DecoratedLWP(LWP):
    def anewmethod(self,x):
        # translate argument and result inches<->mm
        return self.anoldmethod(x*25.4)/25.4
    def anoverride(self,y,z):
        # make sure all calls are logged
        result = LWP.anoverride(self,y)
        z.logit(y,self,result)
        return result

No problem inheriting from LWP -- instantiating
a new DecoratedLWP will instantiate an LWP, but
that's all right, you can instantiate any number of
LWP's you want -- they all internally refer to S,
but who cares.  Inheriting-from-singleton isn't
that easy -- and if two different client modules
want to inherit-from-singleton differently, it's a
big conflict.  So any 'decoration' of singleton must
come through less-convenient encapsulation and
delegation.

A different advantage in C++ is that client code
can instantiate LWP instance with normal operator
'new' (less of an issue in Python, since classes and
functions are both callable in similar ways) and
when done dispose of them with normal 'delete'
(no issue in Python, since references-to-S might
be dropped normally with delete; an issue in C++,
where 'dropping refs to the singleton' would need
to invent one more arbitrary protocol).

A different advantage in COM is that singleton does
NOT work there... not generally.  in-process objects
cannot be system-wide singletons, for example --
not via normal generation methods; their instancing,
exposing and handing out of references would have
to be coordinated from other 'manager' objects. LWP
objects can be in-process or whatever, and handle
everything they may need to coordinate with any
underlying object 'behind the scenes'.


This may be framed as handing out conveniences,
and/or equivalently removing hassles, from the client
code side; basically at minor levels, most of the time,
but these things mount up.  Some of the hassles do
disappear, others may just be moved to server code,
but that's still much better than leaving client code
to deal with them.  It seems to me that Singleton
takes little or no account of the 'library code' vs 'client
code' split.

'Evolvability' of LWP vs S is a particularly strong
point.  If at some point client-code needs to start
receiving references to _more_ than one library-side
object, the passage is much smoother if client code
already expected to receive multiple objects (which
possibly share state) rather than references to a
single one (1 identity/many states is much trickier
than many identity/1 state...:-).


Alex






More information about the Python-list mailing list