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 uses the flyweight and factory patterns to manage the geometric objects created by the user. A geometric object is manufactured once the user has 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 point A and point B again, a new segment is not created, but instead, the existing segment AB is returned. Similarly, if the user selects point B then point A, the same existing segment AB is returned. 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 subclasses, DrGMacroFactory and DrGMathItem, to manage the pool of macro-constructions and mathematics items.
The existing objects are held in a pool of objects:
initialize
pool := OrderedCollection new
when a new object is pushed into the factory15, it is first searched for 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 a 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 as 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 first compare the hash value of each object, 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 if 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 that are mathematically identical through a process of transformations, and only a mathematics solver could establish it.
The items are not built in the factory; this process is factored out in a family of builder classes discussed in the next section.