-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
solidify disposeEmitter pattern #214
Comments
I agree that this is good to discuss. But the examples above for |
Misc thoughts about order... (1) Endeavor to write code where order of disposal doesn't matter. (2) In my experience, order of disposal rarely matters. (3) Rules like "call (4) Adding something like |
If the base type had a I totally agree with (1). Regarding (2)--I'm not sure how much code would break if I went through and scrambled the order of disposals, but it would be an interesting experiment. |
(5) Using Emitter for disposal actions has the advantage that specifying an action (e.g. |
Yes, I understand. Please read this point again. How do you propose to call |
(6) This relies on Emitter's listeners being called in the order that they were added. As I noted in phetsims/graphing-lines#109 (comment):
If order is really important, then relying on the order of Emitter listener's is an ordering dependency that you'll be leaning on heavily. 1239bb6 put us on that trajectory, and it seems like you're willing to leverage that. I think that will prove to be unwise in the long run. |
If everything is done through |
Is this what you're proposing? ...
super();
this.disposeEmitter = new Emitter();
this.disposeEmitter.addListener( ()=> { super.dispose(); } );
...
dispose(){
this.disposeEmitter.emit();
} |
@samreid said:
I don't understand this at all. What is |
Oops, sorry I meant For example: class Supertype{
constructor(){
this.disposeEmitter = new Emitter({reverseOrder:true});
this.linkSomething();
this.disposeEmitter.addListener(()=>this.unlinkSomething());
}
dispose(){
this.disposeEmitter.emit();
}
}
class Subtype extends Supertype{
constructor(){
this.myComponent = new Component(...);
this.disposeEmitter.addListener(()=>this.myComponent.dispose());
this.linkSomethingElse();
this.disposeEmitter.addListener(()=>this.unlinkSomethingElse());
}
// No need to override dispose.
} |
I'm trying to follow you here, but you're making pretty big unexplained leaps. When we started this example, |
In #214 (comment), @samreid said:
OK, so I missed the proposal to move this to "the base type". Are you thinking that |
Right, the pattern identified in #214 (comment) requires either
That doesn't seem like a great fit. Alternatives may be (a) creating a new base type |
After this discussion does it seem like I currently see 8 usages of The base type implementation seems like overkill, and like we are trying to solve a problem we don't have. I would likely feel differently if this was the general pattern that PhET preferred, but it isn't, its a third tier bench-warmer used for special, and already more complex, cases of disposal. That said, and having read the discussion above, if we do go with this pattern, then I think that I prefer the
So to sum up, I outline two options:
What do people think? |
@zepumph asked:
(1) If every class uses (2) Unless you require that constructor( property1, property2 ) {
const disposeEmitter = new Emitter( { reverseOrder: true } );
const property1Listener = ...;
property1.link( property1Listener );
disposeEmitter.addListener( () => { property1.unlink( property1Listener ); } );
super();
const property2Listener = ...;
property2.link( property1Listener );
disposeEmitter.addListener( () => { property2.unlink( property2Listener ); } );
// @private
this.disposeEmitter = disposeEmitter;
}
dispose() {
this.disposeEmitter.emit();
super.dispose();
} Order of setup is: property1, super, property2 Order of cleanup is: property2, property1, super |
I very much like that. RE (2):
such that the above statement would look like: constructor( property1, property2 ) {
const disposeEmitter = new Emitter( { reverseOrder: true } );
const property1Listener = ...;
property1.link( property1Listener );
disposeEmitter.addListener( () => { property1.unlink( property1Listener ); } );
super();
disposeEmitter.addListener(()=>{super.dispose()});
const property2Listener = ...;
property2.link( property1Listener );
disposeEmitter.addListener( () => { property2.unlink( property2Listener ); } );
// @private
this.disposeEmitter = disposeEmitter;
}
dispose() {
this.disposeEmitter.emit();
} |
I missed that, thanks for pointing it out. But I think it's a really bad idea to override something and then chain to the super method elsewhere. |
I think I agree that it is a code smell, and not preferred, in our branching decision tree we have found ourselves out on quite the leaf: And we still have a solution. Before continuing to discuss this edge case it may be worth investigating how many times it occurs in the project. There are 300 usages of |
@zepumph said:
Here's the organization that I typically use (and prefer) for Node subclasses. As soon as we add PhET-iO instrumentation to this code, we will have straddling of class MyNode extends Node {
constructor() {
// Create the scenograph for MyNode
// Create Nodes for subcomponents, some of which need to be instrumented.
super( {
children: [ nodes created above ]
} );
// Now add observers (Property link, addInputListener, etc.)
}
} |
And I most definitely do not want to be forced to change the above to require class MyNode extends Node {
constructor( ..., options ) {
super();
// Create the scenograph for MyNode
// Create Nodes for subcomponents, some of which need to be instrumented.
options.children = [ nodes created above ];
this.mutate( options );
// Now add observers (Property link, addInputListener, etc.)
}
} |
To be clear I meant the usage I found
Can you provide an example of when you have disposed the supertype in the middle of disposing code for the type. I can't find usages so it is hard for me to gauge how much a problem it is. @pixelzoom, since the case outlined in ATYPICAL CASE2 is so offensive, and sounds like it won't work, would you please recommend the form you would like to see this pattern take? |
Note the above commit is just a work in progress, and the section that relates to the dispose emitter pattern will update based on what we decide in this issue. |
@zepumph asked:
I have not had a need to do this. As I mentioned in #214 (comment):
|
@zepumph asked:
I'm afraid that we haven't identified a form that I can get behind. I'm just not a fan of this pattern. It seems to be addressing a problem whose existence I question. We haven't identified a form that doesn't have introduce problems that seem worse than the problem we're trying to solve. And using an Emitter with listeners feels like overkill. |
From discussion on Monday, we don't think that finding a total consensus on this issue is worth the cost. In discussing this pattern, we are trying to nail down a "third tier" pattern, for use after our two more preferred ones. In the design pattern doc it says: Sometimes the above, preferred patterns won't work. For example sometimes components are conditionally created, and
See issue for details on the "dispose emitter" pattern. This seems close enough to a pattern that is reasonable to use if absolutely needed. Closing |
From design patterns conversation today during core hours (phetsims/phet-info#88 (comment)), we discussed using an emitter to perform disposal actions as a "catch all" choice when our more preferred options (
disposeMyType
function in constructor and putting everything on the prototypedispose
method) weren't ideal.Here is an example of the pattern:
Further discussion with @samreid has brought up a few ideas/concerns to discuss.
In dispose order matters, and in general we want to tear down in the opposite order we create, so
this.disposeEmitter
should call its listeners in reverse order. We could support that with an option likereverseCalling
.We could go one step further to generalize this pattern with a new type,
DisposeEmitter
, that does that for you.Without this feature, the disposeEmitter pattern seems brittle and unusable in the general case. Discussion of this is blocking further work in phetsims/phet-info#88
The text was updated successfully, but these errors were encountered: