History for ServicesElementsAndFeatures
??changed:
-
The "Services, Elements, and Features" Pattern
Object-oriented applications tend to involve three major kinds of objects:
* Services - singletons or other well-known instances (e.g. the application object itself, a database server, a CORBA naming server, ZPatterns "Specialists" and "Racks", etc.).
* Elements - application data objects, usually persistent in some way (e.g. database rows, ZODB objects, !ZPatterns !DataSkins, etc.). Usually, these Element objects are associated with one or more Service objects, through which they are accessed, created, and destroyed.
* Features - helper objects that are part of an Element's interface, often shared among all Elements of the same class (e.g. methods and other class attributes). Features may or may not have attributes and methods of their own.
In !TransWarp, Service classes are usually constructed from Aspects, and the classes for Elements and Features are contained in the Aspect's class family. At WeavingTime, FeatureDef objects placed as class variables in the Element classes are converted into instances of the appropriate Feature class. This lets you add aspects which refine or extend certain Feature classes used by the Element classes.
Here's a trivial example showing a "DiceService" aspect whose getDice() method returns a new !DiceElement element object. !DiceElements have a !RandomValueFeature feature, which provides a "next" method to return a random value within the defined range::
from TW.Aspects import Aspect
from TW.Features import FeatureExpression
from whrandom import randint
class DiceService(Aspect):
class RandomValueFeature:
def __init__(self,lo,hi):
self.lo = lo
self.hi = hi
def next(self):
print "Rolling from",self.lo,"to",self.hi
return randint(self.lo,self.hi)
class DiceElement:
diceValue = FeatureExpression('RandomValueFeature(1,6)')
def roll(self):
return self.diceValue.next()
def getDice(self):
return self.DiceElement()
Now let's make two new aspects::
class DoubleDiceService(Aspect):
class RandomValueFeature:
def next(self):
print "Rolling from",self.lo*2,"to",self.hi*2
return randint(self.lo,self.hi)+randint(self.lo,self.hi)
class TenSidedDiceService(Aspect):
class DiceElement:
diceValue = FeatureExpression('RandomValueFeature(1,10)')
You can now add either or both of these aspects to the !DiceService aspect to create a service that returns rolls from 1-10, 2-12, or 2-20, accordingly::
SixSidedClass = DiceService()
DoubleSixClass = (DiceService+DoubleDiceService)()
TenSidedClass = (DiceService+TenSidedDiceService)()
DoubleTenClass = (DiceService+DoubleDiceService+TenSidedDiceService)()
SixSidedClass().getDice().roll() # returns 1-6
DoubleSixClass().getDice().roll() # returns 2-12
TenSidedClass().getDice().roll() # returns 1-10
DoubleTenClass().getDice().roll() # returns 2-20
As you can see, using feature objects lets you decouple different "aspects" of dice throwing - the number of dice from the size of the dice, in this case.
Of course, the above is a ridiculously trivial example. The real power of Features is shown when the same kinds of features are used over and over in an application. For example, consider a "data field" or "property" Feature, which might appear in most Element classes of an application. Seperate aspects could define user interface and database access methods for this "property" class, which would mean every Element would know how to display, input, and save its "fields". (Of course, a real application might have an entire family of property classes, but the aspects used are free to add capabilities to all of those associated classes, or just a few well-chosen bases.)
From UML to Applications
!TransWarp's generative programming tools will use the Service-Element-Feature pattern to create "structural aspects" from UML models. Specifically, a UML package or component will become a Service aspect, and the classes contained in that package or component will then become Element classes within the Service. Finally, the attributes, associations, etc. of each class will become FeatureDef's in the Element classes. At all levels, the classes and FeatureDef instances will be configured with metadata such as their names, multiplicities, attribute types, and perhaps even stereotypes and tagged values.
The result of this should be a highly reusable "structural aspect" which represents only a problem domain, static class model of the application. After domain logic is added, it is only then necessary to define how such features as "attribute" and "association end" will be implemented to create a finished application.
**Need example here, w/trivial UML class diagram and its equivalent structural aspect**
(Interestingly, the most difficult part of implementing !TransWarp's generation framework is in defining good basic, universal interfaces for such Features as "attribute" and "association end". The implementation aspects appear to be quite straightforward to write, a proof-of-concept exists for the aspect generator, and the FeatureDef mechanism already works beautifully, as shown by the library's unit test suite.)