Notes and exercises for learning design patterns
The base class stores one dictionary per subclass:
_shared_states = {}
Then each object redirects its instance dictionary:
self.__dict__ = self._shared_states[cls]
After that, different objects read and write the same attribute storage.
type(self) mattersThis line chooses a shared dictionary for the concrete subclass:
cls = type(self)
That means all Preferences objects share one dictionary, while all Metrics objects share another dictionary.
Without this per-subclass design, Preferences and Metrics could accidentally share unrelated state.
Monostate allows normal object creation. That means __init__ runs for every new object.
But every object points to the same state dictionary. Without this guard:
if getattr(self, "_initialized", False):
return
later objects would reset the shared state.
So the first Preferences(...) call initializes the shared dictionary. Later calls become additional views over the same state.
Singleton:
a = Settings()
b = Settings()
assert a is b
Monostate:
a = Preferences()
b = Preferences()
assert a is not b
assert a.__dict__ is b.__dict__
Singleton shares identity and state.
Monostate keeps different identities but shares state.
Use Singleton when the design rule is:
There should be exactly one object.
Use Monostate when the design rule is:
It is okay to create many objects, but they should behave like views over one shared state.
Monostate can surprise readers because two different objects are not independent. Use it only when shared state is intentional and obvious from the class name or documentation.