I'd like to implement the MVC pattern in a difficult situation. The difficulty is that my Models (the entities generating the events) are long-lived, while the Views (the receivers of the events) are short-lived. My problem is that through the connection between the two, the long-lived Models keep my short lived Views alive, i.e. they cannot be garbage-collected.
[MODEL] ------- <weak> -------> [VIEW]
|
<strong>
|
v
[CONTROLLER]
A way to work around this is to store the connections in the Model in a WeakHashMap<View, Controller>. This essentially lets the View to be garbage collected, and when that happens, the WeakHashMap will throw the corresponding Controller out, too. That is, if the Controller doesn't hold a (strong) reference to the View -- which it usually does. In this ca开发者_如何学Gose the Views are kept alive through the strong references until the Model goes out of scope.
[MODEL] ------- <weak> -------> [VIEW]
| ^
<strong> |
| |
v |
[CONTROLLER] ----------- <strong> ---/
Is there another way to attach listeners to my models that won't keep my views (and controllers) alive?
UPDATE: To answer mdma's question: the Controller keeps a reference to the View, because it needs to update the View. This reference can be weak, but I would like to have the Controllers to be anonymous inner-classes of the View class, in which case the Controller instance has an implicit strong reference to the View instance.
There are a few ways to do MVC.
- Write a model then let your view listen to changes to the model. The view tells the controller when anything happens.
- Write a view then let your model listen to changes to the view. The view tells the controller when anything happens.
- Write a view. Let your model listen to changes to the view. Let your controller listen to the view, which will raise different events if anything happens.
The last one gives the weakest coupling between views, controllers and models. It's a bastard to unit-test the controller because you end up having to stub event handlers. You can't even mock them using mocking frameworks, because you need to raise events the whole time. It does work, though.
I like MVCP:
- Allow your controller to wrap the Model in a Presenter. The Controller listens out for Views being attached, and hands them a new Presenter each time. The Presenter delegates field changes to the Model, and also delegates commands to the Controller. Neither the Controller nor the Model hangs on to a reference to the Presenter. When the view dies, the presenter goes with it.
The great thing about presenters is that you can encapsulate just the stuff that the view needs. The interface for a presenter is almost entirely driven by the view. You can even do stuff like create different presenters for different views, and populate them all through one interface method like this: Presenter.PopulateWith(model, controller)
. This gives you a great place to do all the presentation logic (dates into strings, login names without the .
, etc.) without polluting your lovely Model. And you get your weak reference for free!
This is very similar to the MVVM pattern now used in idiomatic WPF. Works well in Java, also with Web. Hope these give you some ideas, anyway.
Here you've got a great implementation of the MVC pattern. There is probably a solution for your problem.
I think you're very much on the right track. I see two possible solutions to getting views properly reclaimed:
Design the view lifetime into the system, so that view descruction is done explicitly, and all interested parties can then release their references to the view.
Remove the strong reference in the controller. The controller can either use a WeakReference to hold on to the view, which must be checked with each access, or instead, pass the controller a View implementation that delegates to your real view, holding on to it via a weak reference. If the reference has been reclaimed (is null) the method call is a no-op.
... but I would like to have the Controllers to be anonymous inner-classes of the View class, in which case the Controller instance has an implicit strong reference to the View instance.
That simply won't work ... based on the diagrams in the question.
A regular reference in the Model
to the Controller
and another in the Controller
to the View will be sufficient to mean that the View
is strongly reachable. As a result the weak reference in the Controller
to the View
won't be broken ... until the Model
itself becomes eligible for garbage collection.
Since an anonymous inner class can never be static, you have no sensible choice (*
) but to make the Controller a static nested class or a non-nested class.
The other alternative would be to make the link from the Model to the Controller a weak reference.
(*
Actually there is an trick that might possibly work ... though it is almost too horrible to mention. You could figure out what the name of the hidden attribute that holds the Controller object's link to its parent object, and maybe use reflection to find the Field
and then use that to set the attribute to null
.)
EDIT
This is what the JLS says about anonymous classes - JLS 15.9.1
An anonymous class is never abstract (§8.1.1.1). An anonymous class is always an inner class (§8.1.3); it is never static (§8.1.1, §8.5.2). An anonymous class is always implicitly final (§8.1.1.2).
I'm having difficulty reconciling this with the OP's comment ...
精彩评论