Jump to content

"Really" get the class name at run-time

Recommended Posts

37 minutes ago, PaulL said:

The hierarchy can be determined at compile time and stored (not so robust)

What does the inheritance hierarchy look like? Are you saying that there is a class D that *inherits* from class B? I've never seen a state chart implementation where there would be an inheritance relationship between the outer and inner states. That's typically a "has a" not an "is a" relationship. I tried to picture what state (i.e. private data fields) B could have that should be inherited by D... I couldn't come up with any use cases. And the methods are all methods on class State ("Enter", "Exit", "Loopback", etc), not things inherited from B. Please explain how this works. I'm intrigued, as it sounds like a novel approach to state chart implementation. 

Link to comment

This is the State Pattern, not my idea. (There is a link to my full presentation in this thread https://lavag.org/topic/17937-state-machine/#comment-107773.)
Your inclination is correct, in that the States are usually flyweight objects. (This is the case in our implementation.)
The hierarchical relationship allows for override and extension, so that each state has only the code it needs. This facilitates a clear and robust implementation of a true state machine, without repeating behaviors. For instance EnabledState in our implementation processes an error trigger for it and all its substates. In general, debugging is easy because behaviors are precisely specified and occur in only the appropriate place in the code.

The State Pattern is a key part of our solutions.

Link to comment
19 hours ago, PaulL said:

This is the State Pattern, not my idea. (There is a link to my full presentation in this thread https://lavag.org/topic/17937-state-machine/#comment-107773.)

I'm familiar with the State Pattern. I've just never seen the inner states implemented through inheritance of the outer states. I've always seen that as composition. Once I pulled it apart, it made sense. Let me see if I understand:

  • If the transition is from one inner state to another inner state, you call a method X.
  • If the transition is from one inner state to another state outside the outer state, you call a method Y ("StopMotion" in the example). Either that method is responsible for calling an abstract core functions to be implemented by the inner states ("OnInterruptProcessTriggers" in the example) OR (not in the presentation directly but seems to be alluded to) the inner states would override "StopMotion" to add the behavior they need for that transition and use Call Parent to make sure they get the common transition code. 

Assuming I have that right, it all makes sense. What I'm not seeing is why any of the function invocations have to be decided at run time. It's all fixed function calls to match your state machine model. You never have to find the "common parent" for a given transition because every transition has its own method (that is only implemented starting at a particular level of your hierarchy). 

Where does the dynamic come into play? 

Link to comment

A client (the Context) calls one or more methods on an interface State class -- this is an implementation of a Signal trigger. We can also have a trigger based on an internal evaluation of some variable (Change trigger).

Each trigger may result in a response: this could be a state transition, which itself may one or more effects, or it may trigger an update on an ongoing activity.

The implementations inside the various state classes in the hierarchy determine what happens for that state. Yes, they can and frequently do call their parent methods so that common behaviors do not need to be implemented multiple times. (Note that the actual work is delegated to the Model class.)

The UML defines entry, exit, and do methods on a state in a state machine diagram. Entry methods are executed upon transitioning to a class, exit methods when exiting a class, and do methods while remaining in the state. (A 'do' method is often called in a loop and executes in a short time. In fact, the system completes every action  in a very short time, since the responsiveness of the system is defined by this time. Hence we break up long-duration activities into many short actions. The topic here is "run-to-completion.")

The UML also defines self and internal transitions on states. On a self transition, the system leaves the state (executing its exit methods) and reenters the same state (executing its entry methods). On an internal transition, the system does not leave the state so it does not execute the exit and entry methods (just an appropriate do method).

Where reflection comes into play: In the "Challenging" slide there is a transition (marked D to E, but really the trigger would have some other name). If we use the entry/exit method approach, this would execute D.exit(), then E.entry(). For the transition D to F, this would execute D.exit(), then B.exit(), then C.entry(), then F.entry(). In other words, the entry and exit methods execute until they reach the least common ancestor (LCA). In particular in neither transition illustrated do the A.exit() and A.entry() methods execute. The LCA cannot be determined at run-time without reflection (or using more cumbersome, but effective, approaches such as trial and error tests to map the hierarchy).

(In the absence of reflection we invoke the behaviors in the transition code, so do this and that and the other thing, then change state. This works but it does require repeating some code here and there, which has implications for robustness and maintainability. This isn't a major problem, but reflection would definitely make this better.

Link to comment

Paul: Your response is exactly why I thought inheritance is a bad choice here. "D:Exit()" on a state is not a well-defined method because it's behavior depends upon context. Having "D:Exit()" that ONLY does the exit work of D and not the exit work of B or A means that the transition can call "D:Exit()" and then could also call "B:Exit()" if and only if the transition knew itself to be crossing that boundary. Or, better -- it would just call B:Exit() and let B know to call D:Exit(). It's all design time knowledge -- using reflection means that design time information is being computed at run time, which is both inefficient and error prone.

I know that neither of the alternatives I just now suggested fits cleanly into your existing architecture. Your need for reflection is clear to me given your current design. And while I would consider your architecture as a use case if I were working on reflection, I would still push back on the architecture itself. To inherit, you need Identity, State, and Behavior. All three. If you don't have all three, you shouldn't be inheriting. You don't have Identity. State D is not a flavor of state B. It is a substate of B. Any time you miss one of Identity, State, or Behavior but choose inheritance anyway, somethings work, but other things will become significantly more complicated. And a need for reflection is a bright red sign. 

Link to comment
  • 2 weeks later...
  • 8 months later...
On 9/11/2018 at 3:05 AM, Aristos Queue said:

The one main takeaway I had from my project? I support trade embargoes on any nation that tries to use a comma for a decimal point. I know... Britain and the USA are outliers here, but, seriously, it's called a "decimal point"... stop putting a tail on it! I can deal with two different systems for units. I can deal with myriad ways to format a timestamp (even different calendars). I'm supportive of arcane Unicode encodings so that every language ever can be recorded. But I hit my breaking point on commas as decimal points. Why would I want to live in a world where I can order a 1.125 meter steel beam then be charged for delivery of something over a kilometer long?!?! This table should not exist. Yes, my library provides support for such abominations, but it was while adding that feature that I became a strong proponent of cultural hegemony! 

That's because you guys call it decimal point, but in Germany one says "Komma" when you mean to devide the fractional part from the decimal part of a number. So the language is consistent there. (German does also know the word "Dezimalpunkt" but generally the comma is used).

The introduction of the decimal comma started in the 18th century in central Europe, most likely influenced from the French. The English speaking countries continued to use the decimal point. Switzerland is special as one generally talks about a comma but uses a point, but it is not fully standardised in Switzerland (most probably due to the German influence since about half of Switzerland is German speaking). However in nowadays computer settings the standard decimal character in all significant OSes is the decimal point for at least the Swiss German locale.

So broadly speaking the decimal point is used in most English speaking countries and the decimal comma is used in most non-English speaking European countries. While kind of confusing I find that more easily understandable than the myriads of date and time formats used 😀. Not to forget that with date and time you end up with other "trivialities" such as time zones, different calenders and even leap seconds and years.

Edited by Rolf Kalbermatter
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...

Important Information

By using this site, you agree to our Terms of Use.