Allow for future expansion to the set of parameters used to create an object and provide reasonable default values for them.
Often functions that create objects take a list of parameters used to specify characteristics of the new object. Unfortunately, once you initially create a list of arguments, you cannot add new parameters or remove obsolete ones without breaking binary and source compatibility. Also, users don't always want to specify every parameter if they can get away with just using sane defaults.
The Blueprint pattern is applicable in any situation where there are a lot of parameters or when binary or source compatibility must be maintained. Some languages provide support for this pattern natively (Python's keyword arguments, for example).
<insert class diagram here>
A simple object with only setters and getters. Immediately after creation, all attributes should contain reasonable default values.
The function responsible for creating the object takes one extra attributes object parameter. It queries this object for all attributes required to instantiate the object. If an attributes object isn't passed in, the factory operates as if a default attributes object is used.
The user creates the attributes object, filling it with necessary values, and the factory queries them in order to determine how to create the resulting object.
Since the attributes are being passed in via a containing object, an additional level of indirection exists in the system. The attributes object can actually be defined in terms of an interface with QueryInterface- or dynamic_cast-like functionality. This way, not only is it easy to add new attributes to the system, it's easy to specify new implementations of the interface. For example, in a filesystem-like factory like the following:
class IFile { public: // whatever }; class IFileAttributes { public: virtual string getName() = 0; virtual void setName(const string& name) = 0; // UCS-2 virtual wstring getWideName() = 0; virtual void setWideName(const wstring& name) = 0; }; class IFileSystem { public: virtual IFile* openFile(IFileAttributes* attr) = 0; };
Obviously, we can implement these interfaces in terms of standard C or C++ file functions. Additionally, we can implement an FTP filesystem. If we need to specify FTP-specific parameters in the openFile call, we can create an IFTPFileAttributes interface and have the FTPFileSystem implementation do a dynamic_cast to see if the file attributes object implements this extra interface.
In Python, no extra work is necessary, as the language itself provides keyword parameters:
class File: def __init__(self, filename, mode): # do whatever pass def openFile(filename, **kw): if kw.has_key('mode'): mode = kw['mode'] else: mode = some_default_mode return File(filename, mode); file1 = openFile('filename1'); file2 = openFile('filename2', mode='rb');
class IAttributes { public: std::string getName(); void setName(std::string name); }; class Factory { public: IObject createObject(IAttributes* attr) { return new Object(attr ? "" : attr->getName()); } };
Combined with abstract factory, Blueprints can provide a mechanism for specifying attributes of an object with as-yet unknown type.
This is an initial draft, and I'm not all that happy with it. Primarily, I don't know if the name fits. Also, I'd like some feedback on the pattern description and example code. And whether the pattern is useful at all. :)