Observer, Interpreter and Iterator Pattern
Starting Point Analysis
In the Observer Pattern, our prototypes can only accept numeric values for cells. In the Interpreter Pattern, our parser can translate an arithmatic expression into a binary tree. So in the phase, our main purpose is to incorporate the parser into our new prototypes.
Requirement in this phase
This phase is not just merging. If we think it over, there are serveral issues that we never thought before.
- Cyclic dependence should be disallow between the spreadsheet cells. This cyclic dependence may spread over several cell expressions. For example: $1$1=$0$0+1 $0$0=2*$2$2 $2$2=$5%5 $5$5=$1$1+1.
- When a user assign an expression to a cell, in which a cyclic dependence is detected. It is more helpful to inform users the expression cycle rather than say there is a cycle.
- Since one cell could depend on other cells, some other cells may depend on this cell in turn. When one cell is updated, many cell that directly or indirectly related to this cell should also be updated. How to solve this problem nicely.
Strategy for sloving problem
Now there two ways to fulfill the requirements.
- keep all the dependence relationship in the SpreadSheet class
- keep the individual dependence relationship in the Data class.
I prefer the second way since it may be simple and more efficient. So the main issue here is to redesign our Data class.
The data class looks like:
I add two memebers, parents and children, into the Data class. The parents store all refereces to the cells that depended on the current cell. Which means the expressions of these cells referenced to the current cell. The children store all references to the cells on which the current cell depends. The Data class also has a variable reference to the spreadsheet dictionary, so that any Data object can pass this context to its binary tree for evaluation.
The steps to update a cell's tree, we call this cell C :
- For each CellNode in the tree, check whether it directly or indirectly refers C. Indirect reference means one's child or grandchild refers back to C.
- If no cyclic dependence
- update all the old child's parent
- update the current cell's children.
- update the present children's parent.
- If all the present children have a numeric value, then evaluate the current cell. Then notify all the parents to update value. all the notified parents will recursively notifies there own parents.
- return updated information.
Iterator Pattern Overview
The following simple description of interator pattern is from the book, Design Patterns. Please see page 257-271 of this book for more details.
-
Intent
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
-
Structure
Participants
- Iterator
- defines an interface for accessing and traversing elements.
- ConcreteIterator
- implements the Iterator interface.
- keeps track of the current position in the traversal of
the aggregate.
- Aggregate
- defines an interface for creating an Iterator object.
- ConcreteAggregate
- implements the Iterator creation interface to return an instance of the proper ConcreteIterator.
UML Design in this phase
So far, our UML class diagram changed the following figure.
Steps in this phase
- step 1: To find whether there is a cyclic dependence, we have to find all the cell references at first in the tree, which means we have to traverse the tree. To simplify the load of Data class, I sperate this traverse mechanism from Data class. That is why I use the Iterator Pattern. Using this pattern, a client can get all the cell references without knonwing the structure of a tree. So in this step, I modified the Tree class and add a TreeIterator class, which returns the next cell reference in a tree instance.
- step 2: When we inform a user that a cyclic dependence is happened, it is helpful if we can tell the user all the related expressions in some order. To remember a cell's express, we can store the expression string as a member of Data class. Since the Data class already have a tree reference, we can use the Interpreter Pattern again to reconstruct the expression. To do this, I add a __repr__ method to each TreeNode classes. After these two steps, the Tree class looks like this.
- step 3: Implement the Data class using the strategy as mentioned. When a cell is changed, it will notify all its parents to update. The parents is the observers, and the cell is subject. We parents get the notification, they will query the cell state. This is the Observer pattern in essence, although we cannot show the observer pattern structure in the class diagram. This is the reason I call the method notify(). This also tells us that we should bear the essence of each pattern in mind, not the stucture.
After these steps, we have our prototype 7 now.
References
Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides Design Patterns Elements of Reusable Object-Oriented Software Addison-Wesley, 1995