SLE: singletons as modules?

May 21, 2013 15:07

The messes I create in my adventures in codedom.

A singleton class is a class which may only have a single instance, whereas a typical class may have as many instances as you want. Obviously, a singleton is that single instance. A typical object-oriented language like Python doesn't have robust native support for singletons, so you have to write your own system instead. In fact, this is just what I did: I wrote the module sle.singleton specifically to provide and support singleton objects.

There may be many reasons why a developer would want to implement the singleton pattern. For me, these reasons included accessibility - a singleton can be more easily accessed if properly implemented because rather than maintaining or seeking a reference to the instance, you can simply call the class itself; serializability - data can be saved more readily if it is already organized into serializable objects; and extensibility - the functionality of the singleton class can be easily extended by writing a subclass.

However, my implementation ran counter to the most common suggestion for implementing singletons in Python: simply using a module. A module is a top-level object that can contain many classes and functions, among other things, but while it can be easily accessed anywhere in the program, it can't in itself be serialized into saveable data or extended. That's why I chose to do it the way I did: with a metaclass, or a class of classes.

After having tried to elegantly work out the savegame mechanics of the main singletons, the World object (the organizer for the entire game world) and the You object (the player character), I have gotten to wondering if my implementation is really better. While it does work properly, my singletons have a burden I underappreciated, especially World. Because World manages so much data, it would be impractical to serialize the whole thing and save it as a single object to a single file. So World implements a low-end manipulation of the serialization, separating out all the level data and saving them separately from the internal core data of the world. This is a rather sloppy way to do it, really. Like I said, however, if World were just a module, how would I save that core data? Would I have to collect it at save time into some arbitrary object and serialize that?

Well, really, if all that core data has to be collected eventually in an arbitrary object, it might as well reside in an arbitrary object to begin with. That may impede accessibility of the data, though. I would have to work out some way to provide the ease of accessibility that would make the module a useful singleton. If saving is managed by a separate saving object in the first place, maybe this is all moot, but the developer would still have to define or extend the global variable names that must be collected. If suitable, however, it might be easy enough for the saver to just collect every name that doesn't refer to a module, class or function and serialize that.

Then there's the extensibility challenge. You can't subclass a module. But is that such a big deal? The way a module works, you can just modify it on the fly to suit your needs. A constructor function of sorts can easily be overridden or wrapped by another user-written function. Classes and variables can be easily added, and any classes that support the singleton module can be subclassed if need be, although I might have to work out how those classes would be recognized in the singleton.

I guess, ideally, I'd like to make a basic singleton module toolkit of sorts to facilitate the development of such a module by the developer. But then, to maintain accessibility within the SLE, there would need to be some pointer to the user-developed module, or in the least some convention that may be checked.

I'm glad I wrote this out. It gives me a better sense of the challenges I'm weighing.

EDIT (22 May 2:20am): A way to facilitate extension of a singleton module's functionality would be to allow the developer to arbitrarily register data with the base module. The data could be held anywhere the developer wanted, but at save time it would easily be collected in the base module because the core data object would have a pointer to it.

That…is not bad. I still need to chew on it some more, but that seems to straightens out the extensibility hurdle. At least that works for data. I still need to conceptualize how extending classes and functions would work, although as I mentioned such objects can easily be assigned to the module at runtime, overriding the native functionality.

I just have to remember: always aim for elegance. Occam's razor.

EDIT (22 May 2:35am): Thinking a little more about overriding classes and functions: since the native objects in the base module will probably provide core functionality, simply overriding them, as in straight monkey patching, may not be a good idea. They must be wrapped by the overriding object.

This may be easy enough, though. The core objects may be assigned and named separately from their documented API reference names. That is to say, the core object would have two names by default: the name it's defined with as a low-level fixed reference, and a high-level dynamic reference name for the API. Basically, the native high-level object would be overridden, while the low-level object would be wrapped by the new high-level object.

Enough for now. I'm tired.

soulthieves labyrinth engine

Previous post Next post
Up