Post-MVC
An interesting idea has come into my mind. We all play with MVC
and its variations where we have Model and UserInterface
so we end up with code like
public class AlbumController {
//...
protected void updateModel() {
AlbumView1 view = getView();
AlbumModel1 model = getModel();
model.setTitle(view.getTitle());
model.setArtist(view.getArtist());
model.setClassical(view.isClassical());
model.setComposer(view.getComposer());
}
//...
}
This is reasonably if we want Model to know nothing about Views.
But I find myself very comfortable with another idea where an object
is responsible for its own updates and won't let anyone else to
change it. The object should change itself only when it decides
to. When I see code like
model.setTitle(view.getTitle());
model.setArtist(view.getArtist());
model.setClassical(view.isClassical());
model.setComposer(view.getComposer());
I see that AlbumController takes too much care on
model spending too much updating it. I feel that the code really
should belong to model. Definitely, what we have here is code smell
called inappropriate intimacy - http://wiki.java.net/bin/view/People/SmellsToRefactorings
So I come with idea to move this update() code to my Model. At
the same time I still want to keep Model separated from Views.
Now let's take a closer look at our View. Let's suppose we model
some kind of music album store. We have AlbumModel class which
is the actual Model of our Album, and we have AlbumView class which
is visual representation of our Album model.
Can you see something pretty similar between our View and Model?
Our AlbumView contain several text fields like title , artist ,
composer and such which correspond to fields in our Album class.
But with all these fields, we can agree that our AlbumView contain
some kind of model of Album ! Pretty unusual is it? So
we are absolutely able to extract some Album interface from both
AlbumModel and AlbumView
public interface Album {
String getTitle();
String getArtist();
Boolean isClassical();
String getComposer();
}
and make AlbumView implements Album interface
public class AlbumView2 implements Album {
// everything else stays the same!
Now we can move updateModel() ( http://www.refactoring.com/catalog/moveMethod.html )
from AlbumController to AlbumModel while staying no way connected
to AlbumView
public class AlbumModel2 implements Album {
private String title;
private String artist;
private boolean classical;
private String composer;
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public boolean isClassical() {
return classical;
}
public String getComposer() {
return composer;
}
public void update(Album model) {
title = model.getTitle();
artist = model.getArtist();
classical = model.isClassical();
composer = model.getComposer();
fireModelChanged();
}
protected void fireModelChanged() {
// ...
}
}
public class AlbumController2 {
private AlbumView2 view;
private AlbumModel2 album;
protected void updateModel() {
AlbumView2 view = getView();
AlbumModel2 model = getModel();
model.update(view);
}
// ...
private AlbumView2 getView() {
return view;
}
private AlbumModel2 getModel() {
return album;
}
}
Now let's take a look at what we had before and what we have now.
Advantages
Let's suppose you have
public class AlbumController {
//...
protected void updateModel() {
AlbumView1 view = getView();
AlbumModel1 model = getModel();
model.setTitle(view.getTitle());
model.setArtist(view.getArtist());
model.setClassical(view.isClassical());
model.setComposer(view.getComposer());
}
//...
}
This way your model gets updated four times. Four times the model
will fire StateChanged event. This could be time consuming. You
will also find it unacceptable when you need to do an update in
one call to wrap it in a transaction.
Compare it with
public class AlbumController2 {
private AlbumView2 view;
private AlbumModel2 album;
protected void updateModel() {
AlbumView2 view = getView();
AlbumModel2 model = getModel();
model.update(view);
} where the model gets updated only once.
Let's suppose you will go further and try to update your model with one
call to some model.setState(newState) . You create some
class, fill it with data from View and pass it to Model. You need to:
- declare new class;
- instantiate it;
- fill it with data fetched from the View;
- call Model method with new data as parameter;
- your Model will fetch data from given parameter, set its fields and fire
stateChanged event.
Class AlbutModelData {
String title;
...
String getTitle() {
...
}
void setTitle(String newTitle) {
...
}
}
class Controller {
// ****** The action starts here
public void okPressed() {
// Step 1 - create new object
AlbutModelData newModelData= new AlbutModelData();
// 2 - fill it with data entered by the user
newModelData.setTitle(getView().getTitle());
...
// 3 - notify albumModel with new data
getModel().updateState(newModelData)
}
//Album Model
class AlbumModel {
public void updateState(AlbutModelData newModelData) {
// We are not finished, we still need to
// 4 - fetch new state from newModelData
title = newModelData.getTitle();
...
// 5 – fire change event.
fireModelChanged();
...
}
}
Now an improved code
interface Album {
String getTitle();
String getComposer();
...
}
class Controller {
// ****** The action starts here
public void okPressed() {
// Step 1 - controller notifies model
getModel().update(getView());
}
//Album Model
class AlbumModel implements Album {
public void update(AlbutModel newModel) {
// 2 - fetch new state from newModel
title = newModel.getTitle();
...
// 3 – fire change event. This was step (2) before.
fireModelChanged();
}
}
We did a good job having simplified the code while didn't introduce new
dependencies.
Now take a look at part of our Controller
class Controller {
// ****** The action starts here
public void okPressed() {
// Step 1 - controller notifies model
getModel().update(getView());
}
Questions
Will it work for complex situations?
Let's suppose we need to
create solution with role-based restrictions for viewing and updating our
Model. Let's suppose we have users with Reader, Author, Editor and Manager
roles. Reader can read album records. Author is allowed only to view and
edit album records created by its own. Editor have access to all records.
Manager is able to view / change some hidden fields like LastEdited
or AccessList.
Main problem here is that you might end up with a view which is hardly to
implement model interface because it just miss some fields. Messaging a set
of setXXX() methods on your model is not an option because you
want to wrap a change into single transaction. So you might end up with update(param1,
param2, ...) method. Replacing a parameter list with parameter object
we have update(SomeDataSet paramObject) . Here is still a place
for improvement. Instead of creating paramObject, we can extract a corresponding
interface from our View which will be a subset of our Model interface, and
invoke update(PartialInterface viewInstance) on our Model.
Have thoughts? We want to hear from
you! Contact us on our forums. |