From: Stefan F. <ste...@we...> - 2012-07-16 05:02:10
|
After so many details about the foundations, this mail covers best practice for State usage and various State implementations: So this is definitely interesting for all intending to add some serious code to Rails. Stefan I. What are the joint properties of all State implementations? A) Change their Content according to Undo/Redo actions This is the "State" property: All states have their previous (and future for Redo) values stored. This is done transparently both to the Rails user and the Rails developer. (In fact states do not store previous state, instead a list of changes kept, but from outside there is no difference). B) Derived from Observables It is possible to add Observer(s) and Model(s) to them that get updated as soon as the State changes their content. This update not only occurs at the first execution of the change, but every time the State changes due to Undo/Redo actions. C) Derived from Item So every State will have a parent (usually the enclosing class, so use keyword this) and an id. Best practice is to set this identical to the variable name used (see example below). Remark: It is possible to use null as id here, but this creates an "unobservable" state that is mainly used for low-level mechanisms. II. Creation of States States are created using the create() static method and usually there are two variants, either with a (sensible) default or an initial value. Example: private final BooleanState hasFloated = BooleanState.create(this, "hasFloated") (Default here is false) OR private final BooleanState hasFloated = BooleanState.create(this, "hasFloated", true) Best practice for variables containing states is the following: Make them private and final: Private as it is usually better not to expose the State variable itself, but to provide getter/setters. Final is strongly recommended as usually there is no need to create a new State variable (one can simply change their value) AND overwriting a state variable is not an undo/redo proof mechanism itself (see detailed remark at the end of the text). The principle recommendation for Rails variables is the following: Every object can have two sets of variables: a) Static variables They will never change after the time of creation. Use standard java classes and make them final. (Unfortunately this not possible for those objects that get created from XML via the ConfigurableComponents interface). b) Dynamic variables They can change over time. Only use state classes and make them final too. My recommendation for best practice is to group them accordingly for the class definition. Remark: Another reason to make State variables final is to ensure creation at the time of creation of the containing object itself. If it is set to null at creation time, the undo mechanism will never be able to return to that value, best it can do is to return to the initial value of the state variable. So lazy initialization of a State variable is only undo proof, if the program behaves identical if the state variable is set to null or to the initial value at the time of lazy initialization. Rule of thumb is that the null states does not indicate anything special. Rails1.x uses lazy initialization at several places and I have not checked so far if this does not cause some deeply hidden undo-related issues. So make them final prevents such problems by definition. III. State Implementations I distinguish three groups of State implementations in Rails 2.0: First a group that adds a State wrapper around standard variable types (like int, boolean, String etc). Second a group that does the same to various Collection classes (like HashSet, HashMap, ArrayList etc). And third a group with more specialized state behavior (like Portfolio and Wallet). A) Standard States BooleanState, IntegerState, StringState: Wrap Boolean, Integer and String variables Usage: To change a value use state.set(newValue) The current value is returned via state.value() GenericState: belongs to that group to. This allows to wrap any Java class into a State: Usage: GenericState<T> state = GenericState.create(parent, id) where T is the Java class to add State behavior to. Change and viewing the current value use the same methods as above. Example: A variable to store the current player is created by: private final GenericState<Player> currentPlayer = GenericState.create(this, "currentPlayer") The value is changed by currentPlayer.set(nextPlayer) and the value retrieved by currentPlayer.value() B) Collection States ArrayListState, HashMapState, HashSetState (wrap standard Java collections from java.util) ArrayListMultimapState, HashMultimapState (wrap Multimap classes from Google Guava libraries) Short description; ArrayListMultimap<K,V>: Allows duplicate keys and duplicate (key,value) tuples. It replaces a HashMap<K, ArrayList<V>> construct. HashMultimap<K,V>: Allows duplicate keys, but no duplicate (key, value) tuples. It replaces a HashMap<K, HashSet<V>> construct. Usage: Creation follows the standard pattern. At creation time it is possible to use a confirming (standard) collection as initial value to the state variant. Otherwise most of them have the all methods implemented as their counterpart do (and if one is missing, it is possible to add them). Thus usage of the Collection States is identical to their counterparts. Example: private final ArrayListState<Train> listOfTrains = ArrayListState.create(this, "listOfTrains"); listOfTrains.add(newTrain) listOfTrains.remove(oldTrain) if (listOfTrains.contains(train)) do something listOfTrains.isEmpty() listOfTrains.size() A new method available is .view() which returns an Immutable... version of the collection contained. (Immutable variants provided by the Google Guava library). This creates (a lazy - so usually no overhead) copy of the current state of the collection. This makes it easy e.g. to iterate over the list of trains and to change the list inside the loop without creating an error. Recommendation for Iteration is: for (Train train:listOfTrains.view()) do something with trains ... It is also recommended to use the .view() variant for exposure to the outside world, instead of state collection itself, as it prevents unintended changes. C) Specialized States Those are Portfolio (and Subclasses) and Wallet. Details will be covered in a future mail. Short description: A Portfolio allows to store "Ownable" Items (like Certificates, Trains etc.). Differently to all States above a change of the State will not only effect the State itself (or dependent Observers and Models) but also another State: In this case adding an Item to a Portfolio automatically removes it from the previous holding Portfolio. A Wallet does the same for "Countable" Items (like Money etc.). |