• Post Reply Bookmark Topic Watch Topic
  • New Topic

Polymorphism instead of state variables?  RSS feed

 
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm working on a system to store a library of theatrical lighting scenes. The user can build an ordered list of these scenes to use them during a stage play. The idea (hardly original, btw) is that the play might have several scenes in, say, Prof. Higgins's office, and several in Covent Garden. For the most part, all scenes in Prof. Higgins's office will use the same lighting as each other, and all scenes in Covent Garden will use the same lighting as each other. Thus, my library might consist of two Scene objects, where a Scene is a named array of intensity levels for my lights. My cue list (people in this business call it a "cue stack") might have six entries in it, three to set the Scene that lights Prof. Higgins's office, and three to set the Scene that lights Covent Garden. In bare-bones form, I'll have these structures in my model:


For my example, my library contains these two Scenes:

library[0] = <ref. to Scene for Prof. Higgins's office>
library[1] = <ref. to Scene for Covent Garden>


And my cue stack will contain these six Cues:

cueStack[0] = {1000, <ref. to Scene for Covent Garden>}
cueStack[1] = {500, <ref. to Scene for Prof. Higgins's office>}
cueStack[2] = {1000, <ref. to Scene for Covent Garden>}
cueStack[3] = {200, <ref. to Scene for Prof. Higgins's office>}
cueStack[4] = {1000, <ref. to Scene for Covent Garden>}
cueStack[5] = {1000, <ref. to Scene for Prof. Higgins's office>}


Now, an advantage to this approach is that, if the director says, "we need more light on Prof. Higgins's desk," I can change the Scene pointed to by the reference in library[0], and the effect will be that all three uses of that Scene in the cueStack will reflect the change. Of course, it is possible that the director will say, "we need more light on Prof. Higgins's desk, but only in the last scene in his office." In that case, I want to copy the Scene I am using for Prof. Higgins's office, make my change to the copy, and replace the reference at cueStack[5] with a reference to my new Scene (which will also be stored at library[2]).

At any given time, some Scene is always "live" on the stage. So, it makes sense that the user should be able to, say, click a button labeled "Edit Live," and a nice set of sliders or something should appear, each of which is set to match the level of one of the intensities of the currently live Scene. Manipulating the sliders would immediately affect how much light was coming from one of the lights on stage. So, if "Prof. Higgins's office" is currently live, and the director says, "More light on the desk," the user can click, "Edit Live," slide up slider #13 (or whichever one it is), see the light get brighter and, when the director is happy, just save it to the library with the change. If the director only wants the change for one scene (an unfortunate double-use of the word "scene," but that's how it is), then the user must save the changes with something akin to the typical "Save new..." function we apply to files in editors.

In addition to editing the currently live Scene, the user will want to be able to edit any Scene "in the blind," meaning they can open the Scene for editing, but the actual lights on stage don't change while that Scene is being edited. The user will have to imagine the appearance, probably tweaking it at some future time when the actors are off stage and the user can make that Scene live, but an experienced user can get pretty close with their imagination.

So, I must be able to edit the "live" Scene, with immediately visible effect, and I must be able to edit any arbitrarily chosen Scene in the blind. Further, a Scene must be editable either by referring to an entry in the cue stack, or by referring to an entry in the library. If we are editing an entry in the cue stack, the user must have the option to create a new cue and overwrite the entry in the cue stack, while also creating a new entry in the library. If we are editing an entry in the library, the user must still be able to save it as a new entry in the library, but without having any effect on the cue stack. And, the currently live Scene might have been played from either the cue stack, or the library.

Now to my problem: I want to use one and only one editor, and I want to call it with something simple, like Editor.edit(thisCueOrScene). There are four cases it must handle:

1. thisCueOrScene is the currently live Scene and was played from the library.
2. thisCueOrScene is the currently live Scene and was played from the cue stack.
3. thisCueOrScene is not the currently live Scene and was selected from the library.
4. thisCueOrScene is not the currently live Scene and was selected from the cue stack.

The actual editing GUI ought to, I believe, be the same in all four cases. In cases 1 and 2, changes made to the Scene affect the lights on stage immediately (while in cases 3 and 4, the lights on stage don't change). In cases 2 and 4, if a new Scene is created when editing is complete, that new Scene replaces the Scene previously stored in the Cue at the selected location in the cue stack (while in cases 1 and 3, no change is made to the cue stack).

So, my editor code will mostly handle all four cases the same, with two conditional sections: if the Scene is live, change the lights as the Scene is edited; and, if the Scene came from the cue stack, overwrite the Scene in the selected Cue if a new Scene is created. Whatever else the editor does is the same, regardless of those conditions.

I could handle this with state variables, as directly as this:



The first is true when the most recently played cue was selected from the cue stack, and false when it was selected from the library. The second simply records a reference to the currently live Scene. The editor could consult the second one on each change to a setting, transmitting the necessary commands to raise/lower the changed light. It could consult the first one when the user does a "Save new..." operation, copying the new Scene to the selected Cue when a selected Cue was the origin of the current live Scene.

But all this if/then state stuff doesn't feel right. As an alternative, I was thinking of something like this:


One could then overload the edit() method, calling two different versions depending upon whether it was called with a Scene or a Cue. The former would be the actual editor, while the latter would call the editor with the Cue's Scene, and manage the additional cue stack maintenance, if needed, when the editor returned. But I'm still left with the need to update the stage when the Settings object is the currently live Scene or Cue.

It crossed my mind that polymorphism might help if, instead of passing the Scene or Cue to an editor, the Settings class had an edit() method, and this could be overridden by Scene and Cue, and possible also by two other subclasses, LiveScene and LiveCue. Each overridden method would know which state it was to handle, with the caller being responsible for dereferencing a variable of the right type in each case (currentlyLive.edit(), for example). But... that looks like coupling my view (the editor) to my model (the Settings), and that's A Bad Idea, I'm told.

What to do? Is checking that state in a single editor okay, or am I missing a better way?
 
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Imagining myself as a user of the system, when I select a scene to edit and it was the current live scene, I might expect a message or some other indication that I'm about to edit the current live scene. Then I might have an option to see my edits as I make them (call the feature "live preview" maybe?), before saving the settings. Alternatively, you might provide a preview button that allows me to adjust the settings then click the preview button to adjusts the lighting to the settings I selected. I can then choose to apply those settings or continue editing.

Implementation-wise, I'd probably try to apply the observer pattern where the live scene has an observer and non-live scenes don't have an observer. The Scene object can then decide when to trigger an onSettingChanged() event depending on the edit mode the user chose (live preview or manual preview).

It's the observer that would be responsible for taking a Scene object and sending those settings to the lighting control module.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:I'd probably try to apply the observer pattern where the live scene has an observer and non-live scenes don't have an observer. The Scene object can then decide when to trigger an onSettingChanged() event depending on the edit mode the user chose (live preview or manual preview).

Ah, yes! The model can be responsible for issuing notifications. As there can only ever be one live scene at any given time, the observer could be a singleton that sets the next live scene, always registering itself with the model that is going live. That singleton would have to be certain never to have itself registered with more than one Scene model, though. I suppose the observer could register with all Scenes, and the Scenes themselves could check on changes to see if they are the current live Scene, notifying the observer when they are.

Gonna mull that over...
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you're going to do that, consider using the Null Object Pattern.  Your controller class would know which Scene was selected as the live scene and sets that Scene's observer to be the Singleton observer. I don't really see a need to go that far with a Singleton. As long as you just have one instance of the live observer, I don't know that making sure it's a Singleton really buys you anything.  When another Scene becomes the live scene, the one that just got deselected would be re-assigned a Null Object observer that does nothing. That means that your Scene logic doesn't even need if statements to check if there's an observer or not; it just makes its normal calls to the Null Object observer which simply does nothing.
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Internally, you might also consider introducing the idea of "default" settings and "custom" settings.  The default settings would apply to multiple Scenes. The custom settings are the ones that you "Save as new..." and are specific to a particular scene. I might be getting the terminology all wrong here, so please translate accordingly.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:If you're going to do that, consider using the Null Object Pattern.  Your controller class would know which Scene was selected as the live scene and sets that Scene's observer to be the Singleton observer. I don't really see a need to go that far with a Singleton. As long as you just have one instance of the live observer, I don't know that making sure it's a Singleton really buys you anything.  When another Scene becomes the live scene, the one that just got deselected would be re-assigned a Null Object observer that does nothing. That means that your Scene logic doesn't even need if statements to check if there's an observer or not; it just makes its normal calls to the Null Object observer which simply does nothing.


I can see how that would work, but does it have any advantage over the observer unregistering itself? Seems that unregistering from the deselected Scene is about the same as registering a Null Object observer. Well, let me think about that... typically, models might lists of observers, so you avoid the if statements by virtue of the list being empty when there are no observers. But, if a model can only have one observer (certainly the case here), it might be safer to have each model incapable of maintaining a list. In that case, the Null Object makes more sense.

However, this still runs the risk of the observer (or the controller, if I have one) forgetting to register the Null Object with the deselected model. That would effectively have two models both live at the same time. Ideally, I'd like to invert the registration relationship somehow, so the model registers with the singleton observer. If the observer only accepts registration from one model at a time, any new model declared live obliterates the reference to the previous live model. But then I'd have to figure out how to notify the observer which, without it being registered with the model, won't happen.

These things do get complicated fast, sometimes.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Junilu Lacar wrote:Internally, you might also consider introducing the idea of "default" settings and "custom" settings.  The default settings would apply to multiple Scenes. The custom settings are the ones that you "Save as new..." and are specific to a particular scene. I might be getting the terminology all wrong here, so please translate accordingly.


That's actually pretty apt. The greater context of this project is that I want it to be simple enough for a student in middle school (twelve, thirteen, or fourteen years old) to use without anyone teaching them to use it. Also, the existing industry standards are somewhat inflexible, because lighting operators like to be able to move nimbly from theater to theater, and that's hard if everyone's product works differently. Alas, those two contexts are in conflict somewhat. What I'm trying to do is whittle it down the smallest product that would still do a real show of cues, while not departing from the standards, yet remaining facile enough for a young person to get easily.

Shouldn't be any harder than designing a better mousetrap, really.
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:These things do get complicated fast, sometimes.

Because it's all in your head. Your brain likes to run wild. Buck neckid wild.

If you do most of your design thinking in tests, that helps keep things simple. Your brain tends to settle down when it sees code that's complicated and turns back and says, "You know, maybe there's a simpler way to do this..."
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And just because the orthodoxy says that an Observer should register with the thing it's observing, I wouldn't let that stop me from writing setLightingController(LightingController) instead of registerObserver(...).  It's the principle that counts, not the implementation.
 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!