Sei sulla pagina 1di 16

Monostate classes: the power of one

Steve Ball and John Crawford

How can you ensure that all objects of a class share the same state? Why, indeed, would you want to? Surely the point of multiple instantiations is to produce objects that differ in state even while they share the same behavior? Indeed, which problems are solved more easily by using objects that share the same state and differ in behavior? Moreover, how can you leverage C++s inheritance and template mechanisms to facilitate the solution of these problems? Well see that such problems are more common than one might think. This article presents a number of themyou may find the problems surprising familiar. Their solution is presented in the form of a design pattern for the construction and implementation of state-sharing objects. Its a pattern that seems made to be implemented in C++, which offers mechanisms that fit snugly with the contours of the solution.
A SAMPLE SCENARIO Were designing a simulation of an ecosystem to study the ef-

fects of industrial pollution. Among the classes identified by our analysis are those representing such environmental factors as sunlight intensity and oxygen level. These classes have two conspicuous features: Their instantiated state must be unique within the systemalthough the sunlight intensity and oxygen level will fluctuate, its inconceivable that for either environmental factor we could have instantiations that represent independent and different states; contrast this with the ordinary class objects that populate the systemfor example, the Pollutant and Rainforest class objects, which will exist in multiple differing states. Theyre pervasivethey will impact on, or be affected by, almost every other object in the system, and any change in the state of their instantiations must be immediately available to those other objects.
A CONVENTIONAL SOLUTION One acceptable solution (though imperfect, as well

see) is simply to have a global variable of class type for each of these unique objects. Their properties would neatly match the special nature of the items they represented globals are unique objects, their value is readily accessible from anywhere in the system, and a change in their state is instantly propagated. Past experience with global variables would cause many of us to regard this approach with suspicion because of their inability to resist modification by other objects in the system. Ironically, it is this property that makes them suitable as a solution in this scenario, and another problem altogether that makes them undesirable. The problem lies not in their globally accessibility but in devising a suitable impleThe opacity of code that relies heavily on global variables is familiar to many. This tends to be less of a costly maintenance problem with C++ code than with C because global variables may be of class type, so operations that modify them may be encapsulated. Notwithstanding this maintenance liability, it is frequently outweighed (even in C code) by the cost to clarity of constructing variables in a highlevel function and passing them down the function call hierarchy. Often such use of globals is clearer

mentation for the class of which the global objects are instantiations. The real problem then is this: the class is unable to protect itself against having more than one current instantiation. There is nothing to prevent a second object of the same type from being constructed. After all, the class name must be available at global scope and its constructor be publicly accessible for the first object to be constructed. The potential for constructing multiple instantiations of such a class opens the way for violation of two essential principles: The existence of more than one object of the type should be considered logically meaningless. That is, the simulation model goes astray at the point that a second Atmosphere object comes into being, departing from the analogue of the material world. We have all seen wayward data abstractions like these and know that no good comes of them. The implementation of the class should be able to rely upon there being only one object of its type, otherwise there can be no guarantee against misbehavior in the code that implements the class. For example, one could imagine a class acquiring (and locking) a resource in the constructor in such a way that a second attempted construction would cause the program to hang indefinitely. What is required, it would seem, is a way to guarantee, both to the implementer of the class and to its consumers, that at most one object of the type will exist and, by implication, that whenever references are made to an object of the class they are always made to the same object.
THE CONVENTIONAL SOLUTION ELABORATED The perception that global ob-

jects would do the trick if only we could control their propensity to multiply leads us down the road of epicyclic elaboration1. This is the same road taken by early astronomers who sought to retain Ptolemys geocentric paradigm (which attempted to represent planetary motion in terms of uniform circular motions) in the face of evidence of its inability to reconcile new observational data. Additional mechanisms were elaborated to force the paradigm into conformity. Kludge is the less complimentary term that programmers give to epicyclic elaboration. If we choose to follow this rocky road a little further, we find we need to decorate our global objects with an embellishment that forces their constructors to become private (or protected) and inaccessible. The corollary is that we need to nominate a function as a friend (or incorporate a member function to eliminate the requirement for explicit friendship) in order to return a reference or pointer to a single static hidden global variable. Given the spate of recent discussion about the Singleton pattern, youll have already identified that pattern as a traveler on this road2,3,4,5. The Singleton ensure[s] a class only has one instance, and provide[s] a global point of access to it2. However, there are burdens to be borne with this approach. The chief three are: The programmer is obliged to use counter-intuitive syntax to access the objects, Singleton::Instance().mem_func(). As a corollary of the previous requirement, the programmer faces further obfuscation in having to interact with a global object that is never given a namethe object is anonymous and is only referred to by name within the (hidden) implementation of the access function. We shouldnt underestimate the reluctance of programmers to
than the alternatives; consider cin and cout.

use a programming feature or library component they feel they do not understand and perhaps as a consequence wonder if they can trust. The amenability of the class to inheritance is impeded. Proposals to enable subclassing of a Singleton class in C++ produce invasive mechanisms that are a considerable departure from the simplicity and cleanliness of C++s native inheritance mechanism2. (These subclassing mechanisms will be investigated when we discuss Monostate inheritance below.) For these reasons we might consider moving the stricture on instantiation from compiletime access to run-time checkinghave the constructor for the class count the number of instances that exist, and respond appropriately, aborting the construction if necessary3. This has the advantage over private constructors that no special syntax is required to construct or access the global variable. Unfortunately, one is left to choose between the usual options available for failed constructors. Commonly, these are either to place the constructed object in a state that indicates its failed construction (as in ofstream construction) or to throw an exception forcing an exit from the scope that contains the unconstructable object. The former option creates the requirements for tests that make no sense in the problem domain: You mean I have to test whether the Atmosphere object was constructed successfully? If it had failed, wouldnt we all be dead?. The latter approach is favored by Meyers as being more straightforward. However it must also be seen as both punitive and unfair. If consumers should not construct a second instance of the class (and are punished for doing so), why are they given the means to do so?
TAKING A DIFFERENT ROAD TO A SOLUTION A solution that attempts to control

the number of instantiations in the constructor is destined to fail, by definition. One can punish those who attempt one too many constructions, but one has already lost the battlethe constructor has been invoked, the one-object limit is no longer inviolate and reparative action must be taken as a second object now exists. This approach merely creates new problems in an attempt to solve an old one. If we accept that we cannot satisfactorily limit construction (or may not want to), then we must also accept that multiple objects of the class will exist concurrently. How then can we meet our requirements for a class to represent sunlight intensity within our ecosystem simulation? How can we allow unlimited instantiations without violating either the semantic requirement for a guaranteed uniqueness of state or the practical requirement for the implementation code being able to rely on there being ever only one object of that class? What is needed is some way of guaranteeing that a fresh instantiation does not result in a fresh state.
THE MONOSTATE PATTERN The pattern that we propose is non-punitive. This is in

keeping with the philosophical fundaments of the C++ language. C++ deliberately abjures attempts to eliminate programmer error by imposing arbitrary constraints6,7. The Monostate pattern represents a class that can be instantiated any number of times, but whose every instance shares a single state. By imposing no restrictions on construction, it overcomes the problems introduced in attempting to enforce such a limitation, but does so in a way that provides the crucial protection needed by consumers of the class and by its implementer too. It guarantees that: 3

Although there may be more than one object of the class, all objects share the same state. The implementation may rely upon there only ever being a unique state for all objects of the class.
THE SPECIAL CHARACTER OF A MONOSTATE CLASS To create a Monostate

class, we simply make all its data members static:


class Sunlight { public: int lumens() const; // accessors int UVlevel() const; void lumens( int ); // mutators void UVlevel( int ); private: static int _lumens; static int _UVlevel; };

(Youll note that the class has no constructor or destructorthe question of their addition will be addressed later.) Since the totally static character of its member variables restricts this class to a unique state, its natural to think of it as having but a single instantiation. Not sothere is no restriction on the number of instantiations of a Monostate class. This ability of a Monostate class object to endlessly clone itself gives it unique properties: Any number of functions can have their own local Monostate class objectbut all instantiations share an identical state. (Different instances can even be given different identifiers, each acting as an alias for the same object, in the way that a reference is an alternative name for a single object.) A change in the state of one instance instantly propagates to every other instance. In a Monostate-based hierarchy, a change in the state of any derived object will instantly change the state not only every object of that (derived) class, but also of every object of every class in that hierarchy. The size of each object is always one byte, no matter how many data members it has. C++ adds a padding byte to an otherwise vacant structure to ensure that address arithmetic will work with Monostate objects. No matter where its storage is allocatedautomatically, globally, statically, on the heap (or even as a temporary)its state always actually resides in the same place. The object state is persistent between instantiations, and is even maintained during the periods when no object of the class exists. This attribute alone opens the door to surprising efficiency gainsfor example, the construction of a local server object can appear to supply an instantaneous network connection when the actual connection was established once only, in the initial construction. At a stroke, it eliminates the difficulties that arise with the other solutions we have examined: Being locally instantiated, a Monostate class object has none of the problems of unrestricted global access. 4

As a plain vanilla-flavored object, its consumer deals with it using a familiar syntax. Since it allows as many instantiations as the consumer requires, there is no need to invoke a procedure to check the number of instances. Explicit local instantiation signals intended use of its member functions; unlike a Singleton object, a scope is introduced that encloses all operations on the object. This overcomes a type of problem experienced by Singleton objects that is typified by the quest for a means of acquiring locks on their operations8,9. (The utilization of constructors and destructors is discussed in a later section.) By far most the most significant difficulty overcome by the Monostate class, as well as the most significant opportunity offered, is that of subclassing. A Monostate class may participate freely in an inheritance hierarchy without requiring Monostate-specific coding in the implementation of either base or derived classes and without obliging the user of derived classes to cope with unconventional syntax. Dependency between base and derived classes is no greater than between any two conventional classes. As shown below, the breadth of opportunity opened to developers by subclassing Monostate classes is startlingobjects from anywhere in a class hierarchy that share a common Monostate ancestor share part of their state with each other. Modifications to any family member instantly affect all other members. We can characterize the essential difference between the Monostate pattern and those solutions that limit construction by using the analogy of road constructionthe Monostate lays all its roads so that they lead to Rome, while other road-layers place covered pits at the end of all roads that dont end up there. It seems an undesirable and self-defeating practice to threaten your customers with penalties for using your class.
MONOSTATE CLASSES AT WORK Providing state and behavior specialization via inheritance How can we provide

specialized forms of a Monostate class object where the core state is shared with the original object and further enhanced? Suppose, for example, that in our ecosystem simulation, we have a Monostate AmbientTemperature class and we wish also to represent that temperature as modified by Stiples wind-chill factor. We derive an EffectiveTemperature Monostate class:
class AmbientTemperature { public: int fahrenheit() const; int celsius() const; void fahrenheit( int ); void celsius( int ); private: static int _degreesKelvin; }; class EffectiveTemperature: public AmbientTemperature { public: int windVelocity() const; void windVelocity( int ); private:

};

static int _windVelocity;

The effect will be that any change in the state of AmbientTemperature objects will be matched in the state of EffectiveTemperature objects, and vice versa. This, of course, is exactly what we would want. Given that all xTemperature objects must necessarily operate with identical values for _degreesKelvin (for the moment, we ignore the possibility of local variations in temperature), we want both base and derived class objects to march in lockstep. Moreover, if we derive another class from AmbientTemperature, say ShadeTemperature, a change in _degreesKelvin in ShadeTemperature objects will propagate through the hierarchy to EffectiveTemperature objects. Inheritance for Monostate classes is a type of inheritance thats usually not encountered within C++ programmingit is inheritance of object state as much as inheritance of class behavior, and, as such, is a form of delegation7,10. The base class AmbientTemperature is acting as a prototype to which the sub-classes/objects have delegated the storage and management of a portion of their state, while at the same time they extend and specialize the inherited state of the base class/object. The unusual outcome of inheritance in a Monostate class hierarchy underscores the sharp difference between the Monostate pattern and the Singleton pattern (or its instance-counting variant), in which inheritance may defeat the utility of the pattern rather than enhance it, and raises problems to be overcome rather than offering a welcome extension of the patterns benefits2. The nub of the subclassing problem faced by the Singleton class is that if derivation is freely permitted, as for normal classes, the essential nature of the Singleton is lost, since the mechanism disallowing multiple instances applies only to base class objects and is not itself inherited.

Three approaches have been proposed to deal with this problem: 1. Simply outlaw derivation by declaring Singleton base class constructors private. 2. Declare these same constructors protected to permit inheritance but risk losing the behavior, since objects of the base class may be constructed freely (each with a potentially differing state) by perversely constructing them during the base class initialization of a (conceivably empty) derived class. 3. Retain private constructors but at the same time nominate as friends those classes that may derive from the base class. This is merely a constrained variation on the first approach that introduces a dependency between the base and derived classes, but still allows objects to be multiply instantiated into differing states. In the first option, youre cutting off your nose to spite your face, and in other two options, youre confronted with the unattractive prospect of having noses sprout all over your face! For the Singleton class, then, inheritance is futileit strips Singleton of its essence. The best that can be done is to provide a pseudo-inheritance mechanism that is obliged to subvert the typical mode of C++ inheritance in an attempt to mimic subclassing2. This mechanism is implemented using a combination of the second option, protected constructors in the base class, and an implication of the bases Instance() function in the knowledge of its (at least, immediate) descendants. In the case of more than one derived class, the type returned by that function is determined by run-time meansthe Singleton cataloguers suggest registration by name string or the use of environment variables. Further problems arise in choosing the access level of the constructors of the derived classes; the choices are: 4. private or protected: in this case, the base class (or its Instance() function) needs to be given explicit friendship to be able to dynamically allocate an object of the derived type; the relationship between the base and derived classes has progressed from dependency to co-dependency, and further derivation is prohibited without further friendship. 5. public: The singleton behavior of the class is lost; a global point of access is provided to one class instance through the Instance() function, but other instances may freely constructed. Monostate subclassing, which fully embraces the familiar C++ mode of inheritance, has to grapple with none of these problems. Its efficacy can be seen when we consider its use in a quite different scenario.
A second scenario, showing state specialization Monostate inheritance enables us

to model a hardware device that functions in three separate guises. An MC146818 (theres one inside every PC) can be used as a real-time clock, as a store for CMOS settings, and as a programmable frequency interrupt generator. For practical purposes each can be considered and used as a separate device in its own right. To model these devices, it would seem sensible to create three separate types, each with a discrete set of methods. From the design viewpoint, there is no reason to derive these types from a common baseafter all, a clock has nothing in common with a frequency generator. However, there are real implementation benefits to be gained from grouping the three types in a hierarchy that has as its base a Monostate class representing the underlying hardware device. Since each instantiation will share the state of this device, a change in 7

the state of one specialized device should be reflected in a change in the state of each of its siblings, making this a good candidate for private inheritance. Note how different this use of inheritance is from the standard model where a change in the behavior (not the state) of a base object is mirrored in a derived object; here, a change in the state of a sibling (or, in a more extended hierarchy, a child, a cousin, an aunt or a nephew) will effect a modification in an objects own state. To illustrate, we take a minimal definition of a class representing the hardware device:
class MC146818 { public: MC146818(); enum registerEnum{ REGISTER_A, REGISTER_B, REGISTER_C }; unsigned char readRegister ( registerEnum ) const; void writeRegister ( registerEnum, unsigned char ); private: static const int baseAddr = 0x70; static bool updateInProgress; static int interruptType; static volatile unsigned char *_registerA; static volatile unsigned char *_registerB; static volatile unsigned char *_registerC;

};

The implementation of the class initializes _registerA, _registerB and _registerC with literal pointer values into the PC BIOS interface area. The other members, updateInProgress and interruptType that indicate the current internal state of the device are assigned values to match the initial power-up state of the device (termination code returns the state of the device to its initial power-up state, of course). Note that the registers in the PC BIOS interface area are pointed to by pointers to volatile storage. Because the state of the device reflected in the status registers may change at any time, the compiler needs to be warned not to perform any optimizations that eliminate memory fetches. The other data members reflect the attributes of the current state that have been set by operations on the device that may not be determined by querying the hardware. The other classes representing the functions of the device may be derived (privately) from this base class:
class Clock : private MC146818 { public: Clock &operator =( time_t ); operator time_t() const; }; class CMOSSettings : private MC146818 { public: unsigned char operator[]( int byteNo ) const;

};

void setByte( int byteNo, unsigned char );

class interruptGenerator: private MC146818 { public: typedef void (*handler)(); handler setHandler( handler ); void frequency( time_t startTime, time_t period ); };

None of the derived classes writes to or reads from the device directly. As all interaction with the device occurs via the supplied member functions of the base class, the Monostate MC146818 object is able to keep track of the internal state of the hardware device, even though it exists as a base object inside every object of Clock, CMOSSettings and interruptGenerator type. This is the true power of the Monostate objectall objects derived from the same Monostate class share the same state even though they may be of different types. In this example, changes to the state of the device that any CMOSSettings object needs to make in order to change the CMOS settings are recorded in the base MC146818 object so that when any Clock object (there may more than onepick one, any one, dont tell me which) needs to examine the time, the current state of the device is known. Although all instantiations of a Monostate class are essentially aliases for a single object, we are not prevented from using the class as a source of multiple state-differentiated objects. Suppose we wish to allow for the possibility that someone may have two (or more) MC146818 devices. The classes may simply be templated on the basis of the unique interface address that identifies them:
Providing differentiated states via templates

template<int baseAddr> class _MC146818 { ... }; template<int baseAddr> class _Clock : private _MC146818<baseAddr> { ... }; typedef _Clock<0x70> PrimaryClock; typedef _Clock<0x170> SecondaryClock; typedef PrimaryClock Clock; // equivalent to previous example

If were concerned that this mechanism opens the floodgates to a too loosely bounded spate of template instantiations and wed like to keep them within stricter bounds, all we need to do is to parameterize the template over our own defined enumeration rather than over an inbuilt data type:
enum MC146818Id { PRIMARY_MC146818, SECONDARY_MC146818 }; template<MC146818Id id> class _MC146818 { ... };

Although an unlimited number of instantiations of each parameterized class is allowed (each sharing the single state of their particular device), no further _MC146818 types can be created.
Providing type specialization via templates and inheritance We saw above with our EffectiveTemperature derived class that Monostate class inheritance provides

us with specialized objects as well as specialized types. This is in contrast to conventional derived classes that provide only specialized types from which state-differentiated objects can be instantiated. If we do want to achieve the effect of conventional type specialization with Monostate classes, we need to involve templates in the inheritance hierarchy. Suppose that, in our ecosystem simulation, we have a LightLevel Monostate class, and we wish to specialize it into distinct SunlightLevel and MoonlightLevel classes whose instantiations will be state-differentiated. We turn the LightLevel class into a template, and derive from that:
enum light { SUN, MOON }; template <light> class LightLevel { public: int lumens() const; void lumens( int ); private: static int _lumens; }; class SunlightLevel : public LightLevel<SUN> { public: int UVcontent() const; void UVcontent( int); private: static int _UVcontent; }; class MoonlightLevel : public LightLevel<MOON>

10

public: int phase() const; void phase( int ); private: static int _phase;

};

Each derived template class now has its own independent instantiation of the base class, but weve been able in its construction to benefit from the reuse of code as well as express the intended structural relationship between the classes in the hierarchy. Earlier, in our ecosystem scenario, we chose to overlook the possibility of local variations in temperature. Here now, we have a mechanism for representing such variations of the primary abstraction. Its also worth noting that, despite templates and inheritance being sometimes cast in rival roles11,12, here the two mechanisms work together in a strange harmony, helping each other accomplish goals that in this case they are powerless to achieve on their own. LOOSE ENDS
The canonical form of Monostate classes Since the construction of the static mem-

ber variables of a locally instantiated Monostate object will have taken place before the instantiation of the object itself, a constructor can have no part in the objects initialization. It demonstrates this fact at compile-time by rejecting any attempt to provide it with a member initialization list. A constructor may, however, find a use as a mutator and also, as will be seen, as a means of signaling a local instantiation. Similarly, although a Monostate class may provide a destructor, it cannot tidy its static member variables off the scene. They will continue to exist. The problems encountered in implementing Singleton destructors are largely irrelevant for Monostate destructors9. (The next section will present a means of effecting specialized initialization and termination for Monostate objects). Since any two objects of the same Monostate class share the same state, the usual semantics of copy construction and assignment cannot apply, nor is it easy to anticipate what function these operations might perform. At the same time, theres no harm in including a copy constructor and an assignment operator for the sake of completeness. Nor is there any extra expense involved since they may simply be defined to have empty function bodies.
Monostate initialization Occasionally it is not sufficient to initialize the Monostate

class by merely assigning suitable values to its data members. Where this is the case, code such as the following may be added to the module that defines the Monostate class in order to initialize the class in a more computational manner, or to perform more elaborate tidy-up and termination:

11

static struct Janitor { Janitor() { setup(); } ~Janitor() { tidyup(); } } janitor; static void setup() { /* initialization code */ } static void tidyup() { /* termination code */ }

Some linkers, for example that used by IBMs AIX C++ compiler, will link into every executable all modules in used libraries that contain static variables of inbuilt types initialized by function call or static objects with constructors. This can be a problem resulting in potentially enormous initialization overhead and executable-size blow-out. There are more sophisticated alternatives. The so-called Schwarz counter, used to initialize objects cin and cout, is one13. The nature of Monostate classes implies that initialization of the Monostate objects is always performed irrespective of whether the objects are ever used. Paying for potentially unused objects may be seen as spendthrift. The same charge, of course, can be laid against cin and cout, and a similar defense offered. Its in the nature of such objects that theyre essential to the operation of the system in which they appear and the inclusion of the pertinent header expresses a firm intention to use them. In fact the initialization price is lower than the cost that the consumer pays to access Singleton objects, where their initialization flag has to be checked on every occasion2.
Monostate member functions Should member functions of Monostate classes be

static or non-static? Consider how the Monostate object appears to consumers: as an ordinary objecteven though a fresh instantiation borrows from an established state and current instantiations may be modified by operations on other objects of the same or derived classes, the consumer should not be required to deal with the object in any special way. The provision of static member functions dissolves the transparency of the Monostates special attributes. There are other reasons also: static functions may not be defined virtual so derived classes may not override them polymorphically non-static functions ensure that all member functions are invoked in the scope of a locally instantiated object; static functions would subvert any quasi-initialization or serialization mechanisms installed in the constructor and destructor where initial construction is deferred until the time of the first local instantiation, static functions permit indirect access to the objects data members at a time when construction has not yet occurred.
Monostate behavior in a multi-threaded environment Monostate objects exhibit

none of the alarming behavior observed with Singleton objects in a multi-threaded environment since their true construction has already occurred before the execution of main() and the first opportunity to create additional threads8. The problems arise with Singletons because access to them is provided not by name (as for Monostates) but via the non-atomic Instance() function. Schmidt deals with the Singleton problems by elaborating a further Double-Check pattern whose intent is to allow atomic initialization, regardless of initialization order. The nature of Monostate classes ensures atomic initialization, so that no special treatment is required to enable their use in multiple threads. 12

At the same time, Monostate objects do show interesting behavior of their own when instantiated in multiple threads. Because each instantiation is an alter ego of the one object, a Monostate object in one thread can act as a monitor of itself in another thread. The Monostate object in this case would be made const to reflect its role as spectator only.
The final loose end? The astute reader will have perceived a gaping hole in the

Monostate scheme for ensuring that all objects of a class share a single state: there could be more than one program running using the same class, each with its own Monostate object, andhow repugnantwith potentially differing states! True enthusiasts of the One Class, One State credo can enlist the help of shared memory (or DLLs) to ensure that all objects of a class share a single state despite the temporal transiencies of ephemeral programs. Of course, the Monostate attribute of persisting between instantiations (including those times when there are no programs running that use the Monostate class) is as much broadened as the issue of when real construction and destruction occurs. It is left to the same astute reader to propose his or her own solution.
SUMMARY: WHERE TO USE THE MONOSTATE PATTERN At the beginning of

this article, we asked for a means of ensuring that all objects of a class share the same state. We went on to present scenarios where such a Monostate class was the most desirable solution to a problem. It is time now to express the nature of that class using elements of a design pattern, and, where appropriate, to show how this pattern differs substantially from the superficially similar Singleton pattern.
Intent Ensure all objects of a class share a single state, and allow those objects to be

locally instantiated. Contrast this with the intent of the Singleton class, which is to ensure a class has only one instance, and provide a global point of access to it.
Motivation Its important for objects of some classes to share a single state. (The

Singleton pattern states: Its important for some classes to have exactly one instance.) Where the motivation for the Singleton leads its designers to duplicate in the solution domain the single instance in the problem domain, we contend that this design decision takes a too literal approach. The intent and motivation are better achieved by focusing on the single state, rather than the single instance. Examples given for the Singleton class can also serve for the Monostates motivation: a single printer in a system, and a single file system and a single window manager. Indeed, a Monostate object can substitute for a Singleton in most applications. A further motivation for the Monostate pattern is to express relationships between objects that partially share state and functionality, by taking advantage of inheritance hierarchies. One example is a single hardware device that may be accessed in a variety of guises; another is the representation of an ecosystems ambient temperature both as the actual bulb temperature and as modified by a wind-chill factor.

13

Applicability Use the Monostate pattern when:

a number of objects must share a single state changes in that state must be automatically propagated to all objects the single state should be extensible by subclassing, and that subclassing should be carried out without modification of the base class. instances of a Monostate object should have the same look and feel as normal objects.
Structure
Monostate constructor destructor accessors mutators ... other methods .. static data members

Participants The Monostate functions as a normal C++ class, allowing its clients to

instantiate an unlimited number of objects while ensuring that state changes in any one instance are instantly propagated to all others. Contrast this with the Singleton that is obliged to define an Instance operation that lets its clients access its unique instance producing a idiosyncratic species of class, and leaving its users to deal with it via an unnatural syntax.
Collaborations Clients access a Monostate instance as they access instances of any

other classvia its name and public members. By contrast, clients of a Singleton instance are obliged to access it solely through Singletons Instance operation.
Consequences The Monostate pattern has several benefits. In summary, they are:

6. Multiple local instantiations but always with only a single state. 7. Persistent object state in the absence of any instantiations. 8. Familiar syntax. 9. Specialization of object state and behavior via standard C++ inheritance mechanisms. 10. Controlled replication of a Monostate class via standard C++ template mechanisms allowing differentiation of state while retaining behavior. However, there are drawbacks to the Monostate pattern, which include: 11. The fact that sharing is occurring may be overly subtle since all instances of a Monostate class may appear to be unique. 12. The subtlety of sharing can lead to aliasing problems, e.g., calling mutators on one instance of a Monostate object will update all instances. This can cause subtle bugs if programmers dont understand that all the instances are aliases. 14

Implementation Major implementation issues that have been discussed are:

13. Ensuring a unique state for all objects. The crucial step is to declare all data members to be static. 14. Mandating local instantiation. Here, the crucial requirement is that no member functions be declared static. 15. Subclassing a Monostate class. Implementation of an inheritance hierarchy takes place as for any ordinary class and requires no supplementary coding. The effect, however, is to create not simply specialized types but specialized objects. 16. Templating Monostate classes and hierarchies. Templating a single Monostate class will replicate behavior while allowing state differentiation; if it is parameterized over an enumeration, the number of templated classes can be held within defined bounds. Templating the base class in a Monostate hierarchy will express the structural relationship of the derived types and achieve code reuse while allowing state differentiation, as in the familiar use of inheritance.
CONCLUSION: A paradoxical and powerful pattern Monostate classes have an

aura of the paradoxical. They allow local global variables; they produce dynamic static objects; they can have multiple singular instantiations; those instantiations can exist simultaneously in widely separated locations; derivation of the class produces not just specialized classes but specialized objects. The novelty of these paradoxes points up the uniqueness of the Monostate pattern. Its practical application is seen most powerfully when Monostate class objects participate in a hierarchy. Where we have within a system a number of discrete objects that share a portion of their state and which require changes in that state to be instantly propagated to other family members, we have the fullest expression of the Monostate pattern. To implement that pattern, the C++ Monostate class presented here offers a mechanism whose simplicity, cleanliness and paradoxicality mask its power.

15

1 References
1. Polanyi, M. The Stability of Beliefs. THE BRITISH JOURNAL FOR THE PHILOSOPHY OF SCIENCE III (II):217-232, Nov. 1952. 2 2. Gamma, E., R. Helm, R Johnson and J. Vlissides, DESIGN PATTERNS, Addison-Wesley, Reading, MA, 1995. 3. Meyers, S. Bounding object instantiations, Part 1. C++ REPORT 7(3):18-22, Mar./Apr. 1995. 4. Meyers, S. Bounding object instantiations, Part 2. C++ REPORT 7(5):8-12, Jun. 1995. 5 White, R.G. Advantages and Disadvantages of Unique Representation Patterns. C++ REPORT 8(8), Sep. 1996. 6 . Stroustrup, B. THE C++ PROGRAMMING LANGUAGE, 2nd Edition, Addison-Wesley, Reading, MA, 1991. 7. Stroustrup, B. THE DESIGN AND EVOLUTION OF C++, Addison-Wesley, Reading, MA, 1994. 8. Schmidt, D. The Double-Checked Locking Optimization Pattern, Patterns Languages of Program Design, Vol. 3, (Eds. Martin, Buschmann, and Riehle), Addison-Wesley, Reading, MA 1997.. 9. Vlissides, J. To Kill a Singleton. C++ REPORT 8(6):10-19, Jun. 1996. 10 . Wegner, P. Concepts and paradigms of object-oriented programming. OOPS MESSENGER 1(1):7-87, Jun. 1990. 11. Carroll, M. Tradeoffs of Runtime Parameterisation. C++ REPORT 7(9):20-27, Nov./Dec.. 1995 12 . Crawford, J. Response [to Tradeoffs of Runtime Parameterisation]. C++ REPORT 7(9):26, Nov./Dec.. 1995. 13 Stroustrup, B. and Ellis, M.A. THE ANNOTATED C++ REFERENCE MANUAL Addison-Wesley, Reading, MA, 1990.

Potrebbero piacerti anche