[Subversion] / ObjectRoles / README.txt  

Diff of /ObjectRoles/README.txt

Parent Directory | Revision Log

version 2344, Thu Jul 12 17:17:30 2007 UTC version 2395, Fri Oct 26 14:16:23 2007 UTC
Line 2 
Line 2 
 Separating Concerns Using Object Roles  Separating Concerns Using Object Roles
 ======================================  ======================================
   
 In any sufficiently-sized application or framework, it's common to end up  This project has been superseded by the ``AddOns`` package; as it turns out,
 lumping a lot of different concerns into the same class.  For example, you  the term "add-on" is a much better way to describe what the package does.
 may have business logic, persistence code, and UI all jammed into a single  This final release is just a stub that wraps the ``AddOns`` package to provide
 class.  Attribute and method names for all sorts of different operations get  a backward-compatible API.
 shoved into a single namespace -- even when using mixin classes.  
   Please use the `AddOns`_ package in future, by performing the following renames
 Separating concerns into different objects, however, makes it easier to write  in your code::
 reusable and separately-testable components.  The ObjectRoles package  
 (``peak.util.roles``) lets you manage concerns using ``Role`` classes.      Old Name            New Name
       ---------------     ----------------
 ``Role`` classes are like dynamic mixins, but with their own private attribute      peak.util.roles     peak.util.addons
 and method namespaces.  A concern implemented using roles can be added at      Role                AddOn
 runtime to any object that either has a writable ``__dict__`` attribute, or      ClassRole           ClassAddOn
 is weak-referenceable.      roledict_for        addons_for
       role_key            addon_key
 ``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  And of course, you should change your ``install_requires`` to depend on
 possible.  In this way, roles can keep track of ongoing state.  For example,  ``AddOns`` instead of ``ObjectRoles``.
 a ``Persistence`` role might keep track of whether its subject has been saved  
 to disk yet::  .. _AddOns: http://pypi.python.org/pypi/AddOns/
   
   .. contents:: **Table of Contents**
   
   NOTE: The remainder of this document is here to keep testing in place for the
   old API.
   
     >>> from peak.util.roles import Role      >>> from peak.util.roles import Role
   
Line 46 
Line 51 
     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
Line 116 
Line 96 
 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...>}
Line 130 
Line 110 
 are stored under, such that more than one role instance can exist for a given  are stored under, such that more than one role instance can exist for a given
 object::  object::
   
     >>> from UserDict import UserDict      >>> class Index(Role, dict):
     >>> class Index(Role, UserDict):  
     ...     def __init__(self, subject, expression):      ...     def __init__(self, subject, expression):
     ...         self.expression = expression      ...         self.expression = expression
     ...         self.data = {}  
   
     >>> something = Thing()      >>> something = Thing()
     >>> Index(something, "x>y")["a"] = "b"      >>> Index(something, "x>y")["a"] = "b"
Line 144 
Line 122 
     >>> "a" in Index(something, "z<22")      >>> "a" in Index(something, "z<22")
     False      False
   
       >>> Index(something, "x>y")
       {'a': 'b'}
   
       >>> Index(something, "x>y").expression
       'x>y'
   
     >>> dir(something)      >>> dir(something)
     ['__doc__', '__module__', (<class 'Index'>, 'x>y'), (<class 'Index'>, 'z<22')]      ['__doc__', '__module__', (<class 'Index'>, 'x>y'), (<class 'Index'>, 'z<22')]
   
Line 214 
Line 198 
       ...        ...
     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)
Line 505 
Line 489 
     >>> SpecialMethodRegistry(Demo).special_methods      >>> SpecialMethodRegistry(Demo).special_methods
     {'y': 55, 'x': 23}      {'y': 55, 'x': 23}
   
 (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()``.)  keyword argument to ``for_enclosing_class()``, or use the ``for_frame()``
   classmethod instead.  ``for_frame()`` takes a Python stack frame, followed by
   any extra positional arguments needed to create the key.
   
   
   Class Registries (NEW in version 0.6)
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
   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
   provides this behavior, by subclassing ``ClassRole`` and the Python ``dict``
   builtin type, to create a class role that's also a dictionary.  It then
   overrides the ``created_for()`` method to automatically populate itself with
   any inherited values from base classes.
   
   Let's define a ``MethodGoodness`` registry that will store a "goodness"
   rating for methods::
   
       >>> from peak.util.roles import Registry
   
       >>> class MethodGoodness(Registry):
       ...     """Dictionary of method goodness"""
   
       >>> def goodness(value):
       ...     def decorate(func):
       ...         MethodGoodness.for_enclosing_class()[func.__name__]=value
       ...         return func
       ...     return decorate
   
       >>> class Demo(object):
       ...     def aMethod(self, foo):
       ...         pass
       ...     aMethod = goodness(17)(aMethod)
       ...     def another_method(whinge, spam):
       ...         woohoo
       ...     another_method = goodness(-99)(another_method)
   
       >>> MethodGoodness(Demo)
       {'aMethod': 17, 'another_method': -99}
   
   So far, so good.  Let's see what happens with a subclass::
   
       >>> class Demo2(Demo):
       ...     def another_method(self, fixed):
       ...         pass
       ...     another_method = goodness(42)(another_method)
   
       >>> MethodGoodness(Demo2)
       {'another_method': 42, 'aMethod': 17}
   
   Values set in base class registries are automatically added to the current
   class' registry of the same type and key, if the current class doesn't have
   an entry defined.  Python's new-style method resolution order is used to
   determine the precedence of inherited attributes.  (For classic classes, a
   temporary new-style class is created that inherits from the classic class, in
   order to determine the resolution order, then discarded.)
   
   Once the class in question has been created, the registry gets an extra
   attribute, ``defined_in_class``, which is a dictionary listing the entries that
   were actually defined in the corresponding class, e.g.::
   
       >>> MethodGoodness(Demo).defined_in_class
       {'aMethod': 17, 'another_method': -99}
   
       >>> MethodGoodness(Demo2).defined_in_class
       {'another_method': 42}
   
   As you can see, this second dictionary contains only the values registered in
   that class, and not any inherited values.
   
   Finally, note that ``Registry`` objects have one additional method that can
   be useful to call from a decorator: ``set(key, value)``.  This method will
   raise an error if a different value already exists for the given key, and is
   useful for catching errors in class definitions, e.g.:
   
       >>> def goodness(value):
       ...     def decorate(func):
       ...         MethodGoodness.for_enclosing_class().set(func.__name__, value)
       ...         return func
       ...     return decorate
   
       >>> class Demo3(object):
       ...     def aMethod(self, foo):
       ...         pass
       ...     aMethod = goodness(17)(aMethod)
       ...     def aMethod(self, foo):
       ...         pass
       ...     aMethod = goodness(27)(aMethod)
       Traceback (most recent call last):
         ...
       ValueError: MethodGoodness['aMethod'] already contains 17; can't set to 27
   
   
 Threading Concerns  Threading Concerns


Generate output suitable for use with a patch program
Legend:
Removed from v.2344  
changed lines
  Added in v.2395

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help