From: Stefan F. <ste...@we...> - 2012-07-08 20:53:23
|
This installment of the series on refactored classes in Rails 2.0 discusses States and Models. Those are the core of both the undo/redo mechanism and the data/view separation of Rails. I will cover the big picture of States and Models here, more details on the specific States and Models classes to follow in later mails and how to use them will follow in later mails. As it is still on the abstract level, most likely only Erik will comment ;-) Stefan *** Object Hierarchy Item (Interface) | Observable (Abstract Class) | | State (Abstract Class) Model (Abstract Class) | | BooleanState MoneyModel ... ... and many more and many more (in State package) (in Model package) (Remark: Observable above is not java.util.Observable) States being Items implies that they are elements inside the Rails Item tree. Example: hasFloated is a BooleanState of a PublicCompany. So it is possible to locate the hasFloated state of PRR from the root node by calling the locate method: root.locate("/companies/PRR/hasFloated"). And this bypasses the fact that hasFloated and/or its getter/setter methods are declared private. This makes implementing the "exception-to-the-general-rule" rules of some 18xx much easier. *** Core ideas of States and Models State classes usually take existing (or simple) constructs and wraps them with the mechanism required for undo/redo mechanisms. Examples are BooleanState (wraps Boolean), HashMapState (wraps HashMap). Model classes have no redo/undo mechanism of their own, they rely on embedded state objects for that. For this Models can add themselves to States using state.addModel(Model m). If the Model is the parent of the State this is done automatically during construction of the State. An example is a CashMoneyModel which embeds an IntegerState. It is also possible that Model itself is based on another Model, an example for this is PresidentModel which relies on the CertificateModel. By allowing those connections States and Models create a directed graph (in this case a forest == many disjoint trees) with states being the root nodes of each tree in the forest. *** Creation of States and Models The creation of States and Models usually uses the following patterns: State.create(Item parent, String id) to initialize with a (sensible) default value. State.create(Item parent, String id, [Object] init) to initialize with a specified initial value. In some cases (especially models) an id is a predefined and the parent class is specialized. Example: PresidentModel.create(PublicCompany parent) Here the id is automatically set to "PresidentModel" and the parent has to be a PublicCompany. *** Observable/Observer mechanism By deriving from Observable State and Model both share the property that they can be observed by other Rails (usually) GUI elements. Those have to implement the Observer Interface and add themselves to States/Models using state/model.addObserver(Observer o) Observers get only updated once per (Action)ChangeSet. Thus we avoid multiple updates of the GUI during the processing of an action. This update only occurs AFTER closing the open (Action)ChangeSet. This is different to when the State itself changes it value: This occurs immediately after the change of the State. The same is true for Models that are connected with those State, they are informed about the new values, thus could and should update their value as well. Examples: 1. hasFloated BooleanState If hasFloated.set(true) is executed the following happens: * hasFloated changes its value to true (thus hasFloated.value() returns true). * A BooleanChange is added to the current ChangeSet to store that change. * However no update is send to a potential Observer. This is delayed until the ChangeSet closes. 2. CashMoneyModel treasury This stores the amount of cash in companies treasury: It embeds an IntegerState called value to store the amount of money. If treasury.change(-100) is executed the following happens: * The IntegerState value is changed to -100. * An IntegerState change is added to the current ChangeSet. * The CashMoneyModel treasury is informed that the value has changed. * However no update is send to a potential Observers. Again this is delayed until the ChangeSet closes. The reason behind this is to avoid multiple updates of GUI elements during the processing of the GameAction. Only after all processing is finished, the Observers of all effected States and Models get informed only once. It is also possible to roll-back the ChangeSet without having intermediate updates of the GUI. This makes a potential client/server communication much more efficient. *** Comparison to Rails 1.x In Rails 1.x the general hierarchy was: java.util.observable | ModelObject | State So State was a specialized ModelObject. This has been changed, so that State and Model have clearly separated roles, but both are still Observables. Another issue was that several State subclasses did not inherit from State, but from ModelObject or from neither. *** Current State of Implementation Observable/State/Model classes are fully specified, implemented and have unit tests added. I consider the java.util.Observable/Observer implementation is not a perfect fit for Rails, I preferred to write my own instead. However the Observer interface could potentially change, as I am stil working on the needs and constraints of the existing GUI. Currently an Observer only provides an update(Observable obs, String text) method. The latter argument provides a text update for the standard cases. I am wondering if I should add a proxy approach, such that the Observer can specify a function which has to be called that can be specific for each Observable observed. Or if I should be more restrictive and allow each Observer to observe ONLY one observable, thus dropping the need to specify the Observable in the interface method. |
From: brett l. <bre...@gm...> - 2012-07-08 22:01:52
|
On Sun, Jul 8, 2012 at 4:53 PM, Stefan Frey <ste...@we...> wrote: > *** Current State of Implementation > > Observable/State/Model classes are fully specified, implemented and have > unit tests added. > > I consider the java.util.Observable/Observer implementation is not a > perfect fit for Rails, I preferred to write my own instead. Can you expand on this a bit more? What are the limitations that your version overcomes? > However the Observer interface could potentially change, as I am stil > working on the needs and constraints of the existing GUI. > > Currently an Observer only provides an update(Observable obs, String > text) method. The latter argument provides a text update for the > standard cases. > > I am wondering if I should add a proxy approach, such that the Observer > can specify a function which has to be called that can be specific for > each Observable observed. Or if I should be more restrictive and allow > each Observer to observe ONLY one observable, thus dropping the need to > specify the Observable in the interface method. > In general, I would support the more flexible design. However, I'm not certain of the use cases here. Can you give some examples on why we'd prefer one design over the other? ---Brett. |
From: Erik V. <eri...@xs...> - 2012-07-08 22:12:07
|
Stefan, This all looks pretty good to me. > I consider the java.util.Observable/Observer implementation is not a perfect > fit for Rails, I preferred to write my own instead. > However the Observer interface could potentially change, as I am stil working > on the needs and constraints of the existing GUI. Wouldn't it be better to use different class/interface names, to avoid confusion with the standard Java names Observable and Observer? Not that I have any good proposals. Publisher/Subscriber aren't wrong but sound a bit pompously here. Perhaps ObservableItem and ObservingItem or ObservingElement? > Currently an Observer only provides an update(Observable obs, String > text) method. The latter argument provides a text update for the standard > cases. Did you consider the cases where currently a ViewObject is passed? This can (in theory) contain anything that can be serialized (currently to a String only). > I am wondering if I should add a proxy approach, such that the Observer can > specify a function which has to be called that can be specific for each > Observable observed. Or if I should be more restrictive and allow each > Observer to observe ONLY one observable, thus dropping the need to > specify the Observable in the interface method. Not sure what you mean here. Erik. |
From: Stefan F. <ste...@we...> - 2012-07-09 16:09:02
|
Erik, thanks for your comments. Answers see below. Stefan On 07/09/2012 12:11 AM, Erik Vos wrote: > > Wouldn't it be better to use different class/interface names, to avoid > confusion with the standard Java names Observable and Observer? > Not that I have any good proposals. > Publisher/Subscriber aren't wrong but sound a bit pompously here. > Perhaps ObservableItem and ObservingItem or ObservingElement? No I do not think that there is any confusion: That is exactly what java namespaces are for. Did you know that there are 4 other classes named State in packages delivered with the java jdk? What you suggest to not use "State" for that reason? > >> Currently an Observer only provides an update(Observable obs, String >> text) method. The latter argument provides a text update for the standard >> cases. > > Did you consider the cases where currently a ViewObject is passed? This can > (in theory) contain anything that can be serialized (currently to a String > only). > I am aware of those cases, but as I stated, I have not fully decided how to replace them. My preference would be to still have only Strings passed, as this would keep the interface between Observer and Observables easy. In the longer run, the Strings can be complex XML or JSON expression, so it is not a real restriction. As a dirty workaround until a true client/server separation one could even use direct callbacks from the observer to the observable to transmit complex data. >> I am wondering if I should add a proxy approach, such that the Observer > can >> specify a function which has to be called that can be specific for each >> Observable observed. Or if I should be more restrictive and allow each >> Observer to observe ONLY one observable, thus dropping the need to >> specify the Observable in the interface method. > > Not sure what you mean here. > For the latter part of my paragraph above see my answer to Brett. A proxy or delegate approach would add a Delegate object that would subscribe itself to the observable (and would be called on update()). However at the creation of the delegate the true observer could specify that it would prefer to have a different method called. The use case is the following: Assume you have an Observer that observes two Observables called A and B: Then instead of having one method update(Observable obs) and using if statements inside, it is possible to have two methods update_from_A() and update_from_B() inside the observer. But as I prefer to rule that out, there is no need for such an approach. |
From: Stefan F. <ste...@we...> - 2012-07-09 15:43:23
|
Brett, thanks for your comments. See answers below. Stefan >> >> I consider the java.util.Observable/Observer implementation is not a >> perfect fit for Rails, I preferred to write my own instead. > > Can you expand on this a bit more? What are the limitations that your > version overcomes? > In my view (at several more competent others) it is not as easy to use as it can be with your own implementation. The general issues I have, are that is a) non-generic (if one has to use a data object at all), b) confusing to use (you have to do both setChanged and notifyObservers, there is no forcedNotification) and c) the update method of the Observer always always requires one Observable and a data object (which however can be null). More details see e.g. http://www2.sys-con.com/itsg/virtualcd/java/archives/0210/schwell/index.html The rails specific issue is that the list of observers in java.util.Observable is not implemented as state variable, so it is not undo/redo-proof. In general I believe that design patterns should not have a language implementation, as they are suggestions for good coding, not recipes for good coding. Thus you should incorporate the idea into your own code. An excellent implementation (in terms of generality) is the one from PerfectJPattern: http://perfectjpattern.sourceforge.net/dp-observer.html however it is easy to agree that in almost all cases it is a complete overkill. >> However the Observer interface could potentially change, as I am stil >> working on the needs and constraints of the existing GUI. >> >> Currently an Observer only provides an update(Observable obs, String >> text) method. The latter argument provides a text update for the >> standard cases. >> >> I am wondering if I should add a proxy approach, such that the Observer >> can specify a function which has to be called that can be specific for >> each Observable observed. Or if I should be more restrictive and allow >> each Observer to observe ONLY one observable, thus dropping the need to >> specify the Observable in the interface method. >> > > In general, I would support the more flexible design. However, I'm not > certain of the use cases here. Can you give some examples on why we'd > prefer one design over the other? I cannot give concrete examples yet, as I have not started to change the code seriously. However I am getting closer to the opinion that restricting that one observer can only observe one observable (so N:1 from observers to observables) is a good idea: The consequence is that if an Observer wants to observe several Observables at the same time, this could be realized by creating a specific Model for that Observer. And that is exactly the function of a Model that it combines the effect of several other observables (states/models). As I prefer Python over Perl I usually prefer the easier design over the flexible design ;-) |
From: brett l. <bre...@gm...> - 2012-07-09 16:19:38
|
On Mon, Jul 9, 2012 at 11:43 AM, Stefan Frey <ste...@we...> wrote: > As I prefer Python over Perl I usually prefer the easier design over the > flexible design ;-) You and me both. ;-) Your additional explanation helps a lot. I have no objections to the design. Thanks! ---Brett. |