mje Posted August 8, 2011 Report Share Posted August 8, 2011 (edited) I've used some variant of the state pattern for a while now, but never noticed something with regards to its LabVIEW implementation until this weekend. To be clear, by State Pattern, I'm referring specifically to the one detailed byGamma et al: (wikipedia) The idea being that a given Context implements some interface whose behavior changes as a function of State. For behavior which depends on this state, the Context will delegate the request to the contained State object. That is we are defining a behavior through containment, not inheritance. To keep it simple, I won't go into why one would want to do this. In LabVIEW land, this means: Context defines some sort of interface (a set of public or protected VIs) which it wishes to expose to the outside world, this interface need not be made up of dynamic dispatched VIs. A State class will define a similar interface, though this one needs to be made up of dynamic dispatch VIs such that more concrete classes can modify behavior. This interface will often include a Context input/output as part of the VI calls such that the specific State can operate on the Context. Context also contains a variable to store the current State. Note however that during run-time the object stored in this variable is almost always an instance of a more concrete class derived from State (ConcreteStateA or ConcreteStateB in the diagram above). Any call the Context interface delegates the call to the State interface, often passing itself as an argument to the State interface. (Bah, I hit post instead of preview, more to come later...) Edited August 8, 2011 by mje Quote Link to comment
mje Posted August 8, 2011 Author Report Share Posted August 8, 2011 Attached is an example that demonstrates the State pattern. You can open Finite State Machine.vi to see an example of how this is all put together into a trivial FSM. Take note of the Context class. It defines two methods: Operate and Return. The Operate method is responsible for changing the "value" of our class. Depending on the concrete state that is stored in the Context's State variable, an internal Numeric value will be incremented, decremented, or the Context will be set to return. When we request to operate on the Context, the method instead pulls the current State object from its member data, and delegates the request to that State: A given State implementation then decides what it has to do with the supplied Context. There's an important subtlety that needs to be pointed out: This means that a class foreign to Context is deciding how to operate on an instance of Context, meaning Context must expose any operations or properties that are required through a public interface. This can possibly be avoided if we maintain friend relationships between Context and all States, though I can't say I'd recommend as much. Now here's the rub, look at the Context.lvclass:Return.vi method. This VI is intended to decide if the Context is ready to return from some master loop. It looks like it should be able to be made into a property node, since all that the method has is a single return value (boolean), in addition to the class and error I/O. But if you define a new property definition folder in the Context class, and place Return.vi in that folder, you get some errors. In LabVIEW 2010: Invalid accessor VI connector pane pattern. Well that's no help, the connector pane pattern does match. In LabVIEW 2011: Class output terminal does not return the input class. Also not much help, because the input class is returned to the output terminal, but at least we're getting somewhere. I can see this potentially being a problem since the class I/O wire is sent through the State.lvclass:Return.vi interface, and the Context class has no way of determining if the State class will guarantee that a data swap isn't performed on the Context wire within that VI. This can be verified by removing the State.lvclass:Return.vi call from Context.lvclass:Return.vi. Is this intended? The Context.lvclass:Return.vi method is not dynamic dispatch, why should it care about the continuity of the I/O terminals? Something else is afoot here. Is this a bug? State Pattern LV2010.zip Quote Link to comment
PaulL Posted August 8, 2011 Report Share Posted August 8, 2011 I think you hit on most of the key topics with the State Pattern. I have a few hints based on what we have discovered: 1) We also considered making the State classes friends of Context, but we decided not to do that. (For one thing, one has to set up such a relationship for each concrete State class independently, which is not fun. OK, I will go further and say that I don't think the Friends concept is a good idea in general--I consider it a strange solution to a problem that one can better address other ways, and we don't use it.) We did, however, want to have an IContext interface that had only abstract methods, and we wanted to have some methods the State methods could call, and we didn't want to mix the two. (We wanted to have two distinct interfaces.) In the end we made a separate IModel class (an idea a smart colleague suggested) that we put in the private data of Context, and the State methods invoke IModel methods. We have been quite happy with this approach. (The image is from one of the slides in my NI Week presentation.) 2) Note that Model stores the current state information, but not as a state object directly. Rather, we store an enumeration and then use the Factory Method Pattern to create the actual concrete state object. By doing this we make sure the different states do not know about each other, greatly reducing code interdependencies. This reduces build times and dramatically improves the robustness of the build process. (We had working code that did not build when we used the objects directly.) 3) Note that we have lots of Context methods, at least one for each external trigger. For each Context method there is at least one corresponding method on State. After a cursory read of your discussion, I'm not sure I completely understand or like what your Context:Return method is doing. Maybe what you are missing, though, to address your specific concern is a Preserve Run-Time Class function. Paul For the record, the StandbyState.start() method looks like this: The important points are that the state methods have IModel as input and output parameters and invoke IModel methods to do the actual work. Paul Quote Link to comment
mje Posted August 8, 2011 Author Report Share Posted August 8, 2011 Yes, I agree friendship is not the way to go: it's far to difficult to maintain. I also agree that it's usually a bad idea, though there are a few situations where extremely limited scope friendship is useful. As for the model storing the state as an enumeration and invoking things via a factory pattern, that is the exact thing I'm usually trying to explicitly avoid by using the State pattern. At the heart of a factory is some conditional construct that needs to decide what concrete class to use, and is the same argument often used to attack certain very commonly used architectures in LabVIEW. I think the beauty of the State pattern is there is no maintenance of a case structure: the object itself is the mediator of what gets invoked at run-time by virtue of dynamic dispatching. Adding a new concrete state is as easy as extending the state class and the corresponding override behaviors which are relevant to the state then applying that state at what ever scope it is needed.* As for State's needing to be aware of one another, that's only the case if the design decision is made to burden the State descendants with the responsibility of deciding what the follow-up State is. The logic for deciding when to switch states does not need to be in the State hierarchy. (*Of course the dirty little secret is that somewhere the application conditional code needs to exist to decide state context switches. So even by eliminating a monolith by using a dynamic dispatch, we still have an even harder task of maintaining dozens/hundreds of conditional pieces of code that decide on state switches, regardless of where in the pattern the decision was made to make to apply this logic. But I digress...) Note that we have lots of Context methods, at least one for each external trigger. For each Context method there is at least one corresponding method on State. Indeed, the proliferation of VIs from having to maintain mirror methods in two class hierarchies is a negative of the pattern. Though the Context hierarchy is usually quite limited in size. I wouldn't dwell on what the Context:Return method in the example is doing, I was only using it to illustrate a behavior I'm not sure about. If we have a Context method which for all intents and purposes can be treated as a LabVIEW property, should we be able to do so? Something in the IDE is preventing me from doing as much. The real use case is my Context has some properties which are not simple accessor methods, but state dependent calculations. Functionally I have no issue, I can just call the VIs directly by placing the calls on the block diagram. But it would be nice if I could instead have them accessed via the same property node I'm using to collect the 15 other Context properties from... Quote Link to comment
PaulL Posted August 8, 2011 Report Share Posted August 8, 2011 OK, I finally noticed you had attached code. Right. LabVIEW doesn't like it when State can change Context--there is no guarantee that the class in is the class out. I think that using the Preserve Run-Time Class method might help here, but I suggest instead that you reconsider how you use the states. When we first implemented the State Pattern, we had just one State.execute method, and then overrode it. Partly this was because we had a loop that executed when an interrupt occurred. This was a mistake, we discovered. In particular, we realized onInterrupt can be a trigger just like any other trigger. We ended up making multiple methods, something like this: (Simplified version. In an actual system we have more states and a few more methods.) This is orders of magnitude more flexible and lets each State method do one thing. (We also don't have to repeat code in substates.) One fine but important point: ContextThisComponent.onInterrupt actually calls several State.onInterrupt methods [onInterruptGetBasicData(), ..., on InterruptEndLoop(), onInterruptProcessTriggers()], which allows for even more flexibility. The loop runs when an external trigger (a command from the View, or an interrupt--also a command) arrives. In each loop we call the appropriate Context method (which delegates to the State) and it returns directly. Hence we don't need a Return method. We do need to preserve the Context run-time class, though. Here is how we do it: Paul Quote Link to comment
PaulL Posted August 8, 2011 Report Share Posted August 8, 2011 OK, I'm very curious to know where you found the Friends relationship useful (but that is probably a topic for another thread). Yes, the factory method pattern may use a case structure (ours does) but we are still using dynamic dispatch on the State methods, which is the important thing. We first used the factory because we needed to resolve the build issues, but we found we like it because reducing code dependencies helps separate the code in general. I understand your objection, though. In my experience having one factory method has been a positive trade-off, but in all fairness if the LabVIEW compiler handled interdependencies better I probably wouldn't bother. You are quite correct that the states do not need to be the place where the application selects the next states (the Gang of Four book lists this as one option), but I think it is important to reduce the interdependencies wherever one puts that decision code. (We found it simplest and most consistent for our applications to put the state decisions in the state methods.) I also agree that one does need to create rather a few methods to implement an application using the State Pattern. The end result is so clean, though (and each method is so simple), that we have found it well worth the effort. Quote Link to comment
mje Posted August 8, 2011 Author Report Share Posted August 8, 2011 (edited) We ended up making multiple methods, something like this...orders of magnitude more flexible and lets each State method do one thing. (We also don't have to repeat code in substates.) Oh for sure. I didn't mean to imply a State should only have a singular method. The idea is that the Context defines many methods which are ultimately each delegated to a mirror method in the State class. A given concrete State class then overrides methods as required to interpret the requests for that specific State. For any others reading along, I will point out the key difference between our two examples that I see is you have your context defined as something along the lines of: class Context{ Model myModel; State myState; DoSomething() { myState.DoSomething(myModel); }} Whereas I had something along the lines of: class Context{ State myState; DoSomething() { myState.DoSomething(this); }} Some confusion might arise because either of us have a slightly different semantic for what we are calling Context, though if you look at the details, functionally they are very similar insomuch as the Context essentially is the Model in my case. Though I'll add that I often break out and use explicit Context and Model classes as well, the extra class really doesn't add much to the complexity of the system. One thing that confuses me about your post though: One fine but important point: ContextThisComponent.onInterrupt actually calls several State.onInterrupt methods [onInterruptGetBasicData(), ..., on InterruptEndLoop(), onInterruptProcessTriggers()], which allows for even more flexibility. How does this happen? If for each Model you have a corresponding State (whether the State is contained in the Model itself as in my example, or part of a parent Context as in yours), isn't it the State's instance that is deciding which onInterrupt method to call when the request is delegated to State:onInterrupt? Of course that call could end up working it's way up a State class hierarchy, is that what you are referring to? -michael OK, I'm very curious to know where you found the Friends relationship useful (but that is probably a topic for another thread). Oh well, yes, another thread for sure. And likely another day, I've probably sunk too much time into this discussion today! Thank you though for all your feedback so far, Paul. Edited August 8, 2011 by mje Quote Link to comment
PaulL Posted August 8, 2011 Report Share Posted August 8, 2011 One thing that confuses me about your post though: How does this happen? If for each Model you have a corresponding State (whether the State is contained in the Model itself as in my example, or part of a parent Context as in yours), isn't it the State's instance that is deciding which onInterrupt method to call when the request is delegated to State:onInterrupt? Of course that call could end up working it's way up a State class hierarchy, is that what you are referring to? All I mean is this: We have appropriate overrides for each of the State methods. This means the Context's version of onInterrupt is slightly more complex, but it allows us to break down the State behavior into smaller pieces, which makes the State methods many times simpler (and hence easier to maintain). Paul Quote Link to comment
mje Posted August 8, 2011 Author Report Share Posted August 8, 2011 Gotcha. That is a great idea. Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.