The idea of the flyweight pattern is to have objects in one place and to avoid duplication. It is often associated with the factory pattern, so this will be both the place where objects are manufactured or retrieved.
DrGeo use the flyweight and factory patterns to manage the geometric objects created by the user. A geometric object is manufactured once the user provided enough information by selecting a set of existing objects in a sketch.
To create a segment AB, the user selects point A and point B. If the user creates a new segment and selects again the point A and the point B, a new segment is not created but instead the existing segment AB is answered. Identically, if the user selects point B then point A, the same existing segment AB is answered.
This mechanism occurs in the flyweight factory. In DrGeo there are three classes. An abstract DrGFactory:
Object subclass: #DrGFactory instanceVariableNames: 'pool last' classVariableNames: '' poolDictionaries: '' category: 'DrGeo-Factories'
then two sub-classes DrGMacroFactory and DrGMathItem to manage the pool of macro-constructions and mathematics items.
The existing objects are hold in a pool of objects,
initialize
pool := OrderedCollection new
when a new object is pushed in the factory15, it is first searched in the pool,
pushAsLastWhenInPool: anItem
"if this item has a twin in the pool, push as last this last one and return true, otherwise return false" ^ (self findInPool: anItem) ifNotNil: [ :item | self last: item. true ] ifNil: [ false ]
To find in the pool, its index, if any, is searched
findInPool: item
"Try to find a twin of this mathItem, if so return the twin, otherwise nil" ^ self at: (self indexOf: item in: pool)
Determining if an object has a twin depends on the nature of each object. It is done through hash and equality check at the object level
indexOf: anItem in: aPool
"No identity equality but hashed value to detect duplicated object we must consider as equal" ^ anItem ifNil: [0] ifNotNil: [aPool findFirst: [ :each | each hash = anItem hash "double check when hash is equal (can be a collision)" and: [each = anItem] ] ]
If we look at our segment AB example, we want to establish segment AB and segment BA are the same mathematics object.
In DrGeo, the geometric object model uses the template method pattern to establish how equality is articulated:
DrGMathItem>>= aMathItem
^ aMathItem isMathItem and: [self basicType == aMathItem basicType and: [self nodeType == aMathItem nodeType and: [self parentsEqual: aMathItem] ] ]
For a segment defined by two points, basicType is #segment and nodeType is #’2pts’.
The subclasses of DrGMathItem implement the parentsEqual:. In DrGSegment2ptsItem we cross check if the extremities of the segments are the same points:
parentsEqual: aMathItem
^ parents asSet = aMathItem parents asSet
Comparing can be slow, therefore we compare first the hash value of each objects, pre-computed:
rehash
^ hash := (parents asSet hash bitXor: self nodeType hash) bitXor: self basicType hash
These details show the tricky part in this pattern: how to establish two objects are identical. With objects representing mathematics items it is difficult, the identity check done by DrGeo is very limited. After all, we could have two segments mathematically identical through a process of transformations and only a mathematics solver could establish it.
The item are not built in the factory, this process is factored out in a familly of builder classes discussed in the next section.