Separating Concerns Using Object Roles |
Separating Concerns Using Object Roles |
====================================== |
====================================== |
|
|
(New in version 0.6: ``ClassRole.for_frame()``, ``Registry``) |
This project has been superseded by the ``AddOns`` package; as it turns out, |
|
the term "add-on" is a much better way to describe what the package does. |
|
This final release is just a stub that wraps the ``AddOns`` package to provide |
|
a backward-compatible API. |
|
|
|
Please use the `AddOns`_ package in future, by performing the following renames |
|
in your code:: |
|
|
|
Old Name New Name |
|
--------------- ---------------- |
|
peak.util.roles peak.util.addons |
|
Role AddOn |
|
ClassRole ClassAddOn |
|
roledict_for addons_for |
|
role_key addon_key |
|
|
In any sufficiently-sized application or framework, it's common to end up |
And of course, you should change your ``install_requires`` to depend on |
lumping a lot of different concerns into the same class. For example, you |
``AddOns`` instead of ``ObjectRoles``. |
may have business logic, persistence code, and UI all jammed into a single |
|
class. Attribute and method names for all sorts of different operations get |
.. _AddOns: http://pypi.python.org/pypi/AddOns/ |
shoved into a single namespace -- even when using mixin classes. |
|
|
.. contents:: **Table of Contents** |
Separating concerns into different objects, however, makes it easier to write |
|
reusable and separately-testable components. The ObjectRoles package |
NOTE: The remainder of this document is here to keep testing in place for the |
(``peak.util.roles``) lets you manage concerns using ``Role`` classes. |
old API. |
|
|
``Role`` classes are like dynamic mixins, but with their own private attribute |
|
and method namespaces. A concern implemented using roles can be added at |
|
runtime to any object that either has a writable ``__dict__`` attribute, or |
|
is weak-referenceable. |
|
|
|
``Role`` classes are also like adapters, but rather than creating a new |
|
instance each time you ask for one, an existing instance is returned if |
|
possible. In this way, roles can keep track of ongoing state. For example, |
|
a ``Persistence`` role might keep track of whether its subject has been saved |
|
to disk yet:: |
|
|
|
>>> from peak.util.roles import Role |
>>> from peak.util.roles import Role |
|
|
saving |
saving |
>>> Persistence(aThing).save_if_needed() # no action taken |
>>> Persistence(aThing).save_if_needed() # no action taken |
|
|
This makes it easy for us to, for example, write a loop that saves a bunch of |
|
objects, because we don't need to concern ourselves with initializing the |
|
state of the persistence role. A class doesn't need to inherit from a special |
|
base in order to be able to have this state tracked, and it doesn't need to |
|
know *how* to initialize it, either. |
|
|
|
Of course, in the case of persistence, a class does need to know *when* to call |
|
the persistence methods, to indicate changedness and to request saving. |
|
However, a library providing such a role can also provide decorators and other |
|
tools to make this easier, while still remaining largely independent of the |
|
objects involved. |
|
|
|
Indeed, the ObjectRoles library was actually created to make it easier to |
|
implement functionality using function or method decorators. For example, one |
|
can create a ``@synchronized`` decorator that safely locks an object -- see |
|
the example below under `Threading Concerns`_. |
|
|
|
In summary, the ObjectRoles library provides you with a basic form of AOP, |
|
that lets you attach (or "introduce", in AspectJ terminology) additional |
|
attributes and methods to an object, using a private namespace. (If you also |
|
want to do AspectJ-style "advice", the PEAK-Rules package can be used to do |
|
"before", "after", and "around" advice in combination with ObjectRoles.) |
|
|
|
|
|
.. contents:: **Table of Contents** |
|
|
|
|
|
Basic API |
Basic API |
one (or is a new-style class with a read-only ``__dict__``), they are stored in |
one (or is a new-style class with a read-only ``__dict__``), they are stored in |
a special dictionary linked to the subject via a weak reference. |
a special dictionary linked to the subject via a weak reference. |
|
|
By default, the dictionary key is a one-element tuple containing the role |
By default, the dictionary key is the role class, so there is exactly one role |
class, so that there is exactly one role instance per subject:: |
instance per subject:: |
|
|
>>> aThing.__dict__ |
>>> aThing.__dict__ |
{<class 'Persistence'>: <Persistence object at...>} |
{<class 'Persistence'>: <Persistence object at...>} |
... |
... |
AttributeError: 'NoDict' object has no attribute '__leech__' |
AttributeError: 'NoDict' object has no attribute '__leech__' |
|
|
Of course, if an object doesn't have a dictionary *and* isn't weak- |
Of course, if an object doesn't have a dictionary *and* isn't |
referenceable, there's simply no way to store a role for it:: |
weak-referenceable, there's simply no way to store a role for it:: |
|
|
>>> ob = object() |
>>> ob = object() |
>>> Leech(ob) |
>>> Leech(ob) |
Alternately, you can pass a specific Python frame object via the ``frame`` |
Alternately, you can pass a specific Python frame object via the ``frame`` |
keyword argument to ``for_enclosing_class()``, or use the ``for_frame()`` |
keyword argument to ``for_enclosing_class()``, or use the ``for_frame()`` |
classmethod instead. ``for_frame()`` takes a Python stack frame, followed by |
classmethod instead. ``for_frame()`` takes a Python stack frame, followed by |
any additional arguments needed to create the key. |
any extra positional arguments needed to create the key. |
|
|
|
|
Class Registries |
Class Registries (NEW in version 0.6) |
~~~~~~~~~~~~~~~~ |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
For many of common class role use cases, you just want a dictionary-like object |
For many of common class role use cases, you just want a dictionary-like object |
with "inheritance" for the values in base classes. The ``Registry`` base class |
with "inheritance" for the values in base classes. The ``Registry`` base class |