Re: Can use of singletons denote poor project design?



Responding to Phlip...

John Fly wrote:


I've found the use of singleton objects quite nice for various aspects
of this program, I do enjoy having the ability to use a singleton class
by using the Class::Instance() syntax, thus removing the need to pass
instances around in all my function calls. Also very useful is the
ability to use a guarenteed single instance by simply including the
header containing my singleton.


The most important design feature is testability. Classes must rigorously
decouple from each other to submit to test cases with very fine
granularity.

(You do _have_ test cases, don't you?)

I don't think using Singleton has anything to do with testing. Singleton is a response to a problem space constraint where there can only be one object created from a particular class AND in the normal course of processing there may be multiple opportunities to create such an object. Multiple opportunities may arise in iterative processing of the same creation code or by having separate creation code (or triggers of the creation code, such as invoking the same factory object from different solution contexts) for different contexts in the solution.

Note that if there must only be one instance but there is only one opportunity to create it (e.g., as part of startup initialization) then there is no need for the complexity of Singleton because there is no way to create additional objects in the normal course of execution. That is, the solution flow of control naturally enforces the rule.

Unfortunately Singleton tends to be overused. In particular there are two common abuses. The first is simply to introduce global data. Occasionally there will be problem space entities that have an intrinsic responsibility to manage state data for other entities, but those are pretty rare. More commonly the developer just takes the easy way out and creates a Singleton rather than properly allocating the responsibilities, properly instantiating relationships, and managing instance creation itself.

The second abuse is the one described by the OP where the developer is trying to avoid passing object references around. Avoiding passing object references is a very good idea, but Singleton is not the way to do that. Passing an object reference is essentially instantiating a temporary relationship that exists only for the duration of the call. That has two drawbacks. First, it requires the caller to understand the receiver's relationships. IOW, we have:

[Client]
| 1
|
| R1
|
| 1
[Service]
| 1
|
| R2
|
| 0..1
[OtherService]

Client navigates R1 to collaborate with Service by sending Service a message. Service, in turn, needs to collaborate with OtherService by navigating the R2 relationship with a message. When Client includes the OtherService reference in its message to Service, Client is essentially establishing the R2 relationship. That means Client must understand the rules and policies for relating [Service] to [OtherService] and typically that is none of Client's business. IOW, the R2 relationship is a personal matter between [Service] and [OtherService].

In addition, the rules and policies for instantiating /participation/ in relationships are quite often different than the rules and policies for collaboration between objects. IOW, relationship instantiation is about Who should collaborate but relationship navigation is about When to collaborate. Usually those disparate sets of rules and policies are encapsulated in different objects.

The second problem is that for temporary relationships at least one end of the relationship must be conditional. Conditional relationships lead to more complex code and additional overhead so one should avoid them whenever it is feasible. (Sometimes they can't be eliminated, but the reviewer is going to want a good reason why they can't.) That provides a clue as to how one avoids passing object references without using Singleton.

The proper solution lies in defining the relationships in a better fashion by doing things like eliminating conditionality. More important, though, are the basic OOA/D notions that (A) a behavior is self-contained so it doesn't directly depend on what other behaviors do and (B) that a behavior accesses the data it needs on an as-needed basis by navigating relationships paths _that it already has_. Thus when Service's method it invoked, it should navigate some existing relationship path to get what it needs from OtherService -- in this case the R2 relationship (e.g., via it own private myOtherService reference).

There is some problem context that prevails when Client sends its message to Service. That context will determine that Service will need to collaborate with a particular OtherService. So as soon as that context prevails the R2 relationship should be instantiated so that Service will get to the right OtherService. That will context will prevail /before/ Client collaborates with Service. (In fact, for Client to provide the OtherService reference that will already be true.) The developer just needs to back-track to to point where the context prevails and instantiate the R2 relationship at that point in time.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
(888)OOA-PATH



.



Relevant Pages

  • Re: Singletons
    ... Such attributes are usually initialized by the constructor or a factory object when the object in hand is created. ... There are actually two objects involved in Singleton. ... To access that method there must already be a relationship that can be navigated by the client to get to the Singleton object to invoke GetInstance. ... When Singleton passes back the reference to Client it is temporarily instantiating the R3 relationship between Client and the Collaborator that Client navigates when it talks to Collaborator. ...
    (comp.object)
  • Re: Singletons
    ... By modifying the context I meant that one defines the solution flow of control differently so that the instantiation can be done in one place rather than in several. ... Singleton is really just a specialized factory object. ... There is no way for the client to know whether the instance returned is one of one or one of many. ... Because many network protocols limit the size of the message so it has to be split up into separate packets. ...
    (comp.object)
  • Re: beginner Qs about COM/.NET (context = exposing a pre-existing app to scripting)
    ... You can even use a C++ singleton class. ... > objects into a script variable, and lets it sit on the shelf, my server ... it is the client that determines when to call Releaseon your object. ... > I think I have to use MTA; while program A is using the serial port, ...
    (microsoft.public.vc.atl)
  • Re: Intermittent Remoting Event Callback Problem
    ... OPCEngine published component that was originally a singleton. ... requested by the client through a single-call class factory. ... to reconnect to the server and re-register the callbacks when an error ... the callback *STILL* fails. ...
    (microsoft.public.dotnet.distributed_apps)
  • Re: Opinions on the Law Of Demeter
    ... > But the point of combining the factory and singleton was to be able to ... > the client a reference to the factory without the client knowing whether ... >> LoD, ... e.g. connection pooling, we all used to pass around connections, and then ...
    (comp.object)