Sure, we’ve all heard of Singleton, the design pattern that limits the number of instances of a class to one. It’s a feel good pattern because it’s easy to use and make us feel smarter in front of our co-workers, even though some would argue that Singletons are just glorified global variables. But for a long time, I have wondered if it would be possible to use the same idea to limit the number of instances to two or more instead of just one. I called this hypothetical pattern an “N-gleton”, pronounced “en-gull-ton”. (UPDATE: Comments from readers inform me that this is called a “Multiton“) It may not be possible to make a poly-instance singleton, it may not be good programming practices, I’m not even sure I can think of a reason why it might be useful, but today, I tried my hand at inventing it and found something else entirely.
The Hard Limit approach
The first attempt I made at the N-gleton was to simply throw an error when the class had been instantiated too many times.
package {
public class NGletonHardLimit {
protected static var maxInstances:int = 3;
protected static var numberOfInstances:int = 0;
public function NGletonHardLimit() {
if (numberOfInstances >= maxInstances) {
throw new Error ("There can only be " + maxInstances + " instances of this class");
}
numberOfInstances++;
// initialize code
}
}
}
This was shortly followed by a slightly more friendly version of the same idea…
package {
public class NGletonRequestInstance {
protected static var maxInstances:int = 3;
protected static var numberOfInstances:int = 0;
public static function requestInstance():NGletonRequestInstance {
if (numberOfInstances >= maxInstances) {
return null;
} else {
return new NGletonRequestInstance(new Key());
}
}
public function NGletonRequestInstance(key:Key) {
// initialize code
}
}
}
class Key {}
However, I soon realized that this method is quite flawed in its logic. The counter is actually just counting the number of times the requestInstance() method is being called rather than truly controlling the number of instances of the class. It also doesn’t allow you to release an instance and decrement the instance count. Perhaps something like this would work in C where you can use alloc and dealloc but for Flash, it doesn’t really make sense. Besides, It doesn’t really seem useful.
Cycling through instances
Next, I tried looping through the same limited set of instances. Each time you request an instance you get a new one until you’ve reached your limit, then it starts over with the first instance again.
package
{
public class NGletonLoop
{
protected static var maxInstances:int = 3;
protected static var numberOfInstances:int = 0;
protected static var instances:Array = [];
public function NGletonLoop(key:Key) {
// initialize code
}
public static function requestInstance():NGletonLoop {
var n:int = numberOfInstances++ % maxInstances;
if (instances[n] == null) {
instances[n] = new NGletonLoop(new Key());
}
return NGletonLoop(instances[n]);
}
}
}
class Key {}
This approach just felt wrong. In fact, it was totally stupid. There is no way to know what you’re going to get when you request an instance and it seems like it would be very easy to overwrite data without even knowing it. You still have the problem of the instantiation being counted rather than the actual objects. Besides, it’s really totally useless.
It was looking like the whole idea of an N-gleton may not really have a practical application. But in the name of theoretical computer science, I chose to press on and try one more idea I had.
Tracking individual instances
The previous idea of storing instances in an Array seemed to make some kind of sense to me but it was missing individual control over the instances. So I decided to try using an index to track the instances. I soon realized that a Dictionary that uses objects as keys might give a more useful identifier than an integer. The maximumInstances didn’t really make sense any more so I took it out. I seemed to be onto something but it wasn’t really the N-gleton I had imagined, it was something more useful. I called it an IndexedObject.
package {
import flash.utils.Dictionary;
/**
* An example of a class that tracks multiple individual instances by using a dictionary.
*/
public class IndexedObject {
/**
* Stores the actual instances of the class.
*/
protected static var _instances:Dictionary = new Dictionary(false);
/**
* The identifier that the dictionary uses to reference this instance.
*/
public function get id():* { return _id; }
protected function set id (id:*):void { _id = id; }
protected var _id:*;
/**
* Gets an instance of the class. Similar to how a singleton would get the
* instance but uses an identifier token to specify which instance to get.
*/
public static function getInstance(id:*):IndexedObject {
if (_instances[id] == null) {
_instances[id] = new IndexedObject(new Key)
}
var instance:IndexedObject = _instances[id] as IndexedObject;
instance._id = id;
return instance;
}
/**
* Returns all of the created instances as an array.
*/
public static function get allInstances():Array {
var array:Array = [];
var obj:IndexedObject;
for each (obj in _instances) {
array.push(obj);
}
return array;
}
/**
* Removes an item from the list of instances.
* Actually, doesn't quite work because an instance referenced by a variable
* outside of the class could still hold a reference to the deleted instance.
*/
public static function deleteObject(id:*):void {
delete _instances[id];
}
/** Just used for testing */
public var name:String;
/**
* Constructor can't be used by outsided classes.
*/
public function IndexedObject(key:Key) {
// Initialize code
}
}
}
class Key{}
So essentially what’s happening here is the Singleton’s getInstance() method is being replaced with one that allows you to specify a particular instance. Any class could create new instances or access the same instances provided they had the same id. Here’s a little demo. I’m using a name property to show the example.
var thing:IndexedObject = IndexedObject.getInstance(5);
thing.name = "thing 5";
thing = IndexedObject.getInstance("foo");
thing.name = "thing foo";
for each (var obj:IndexedObject in IndexedObject.allInstances) {
trace("IndexedObject.getInstance(",obj.id,") =", IndexedObject.getInstance(obj.id).name);
}
Results in:
IndexedObject.getInstance( foo ) = thing foo
IndexedObject.getInstance( 5 ) = thing 5
Voila. The “indexed object”. I think for me the jury is still out on whether this is a good idea. It seems like it might be kind of a hack since it, like Singleton, is essentially just a complicated implementation of global variables but even more so and without Singleton’s benefits. It also intuitively feels like it could be very useful. Either way, I’m proud of it.
So what do you think? What would you use this for? Is it a hack? Pattern or anti-pattern?