There are no commonly-agreed-upon (i.e. defacto) design guidelines for MVC. It is not that entirely hard to do it yourself but requires some planning on your classes and lots of time and patience.
The reason there is no definite solution is because there are multiple ways to do MVC, all with their pros and cons. So just be smart about it and do what suits you the best.
To answer your question, you actually want to decouple the controller from the view as well (so you can use the same business rule logic for both a Swing app and in the console app). In the Swing example you want to decouple the controller from the JWindow
and whatever widget in Swing. The way I used to do (before using actual frameworks) is to create an interface for the view that the controller uses:
public interface PersonView {
void setPersons(Collection<Person> persons);
}
public class PersonController {
private PersonView view;
private PersonModel model;
public PersonController(PersonView view, PersonModel model) {
this.view = view;
this.model = model;
}
// ... methods to affect the model etc.
// such as refreshing and sort:
public void refresh() {
this.view.setPersons(model.getAsList());
}
public void sortByName(boolean descending) {
// do your sorting through the model.
this.view.setPersons(model.getSortedByName());
}
}
For this solution during startup you need to register the controller into the view.
public class PersonWindow extends JWindow implements PersonView {
PersonController controller;
Model model;
// ... Constructor etc.
public void initialize() {
this.controller = new PersonController(this, this.model);
// do all the other swing stuff
this.controller.refresh();
}
public void setPersons(Collection<Person> persons) {
// TODO: set the JList (in case that's you are using)
// to use the given parameter
}
}
It might be a good idea to create an IoC-container to do all the setup for you instead.
Anyhow, this way you can implement console-only views, using the same controllers:
public class PersonConsole implements PersonView {
PersonController controller;
Model model;
public static void main(String[] args) {
new PersonConsole().run();
}
public void run() {
this.model = createModel();
this.controller = new PersonController(this, this.model);
this.controller.refresh();
}
public void setPersons(Collection<Person> persons) {
// just output the collection to the console
StringBuffer output = new StringBuffer();
for(Person p : persons) {
output.append(String.format("%s%n", p.getName()));
}
System.out.println(output);
}
public void createModel() {
// TODO: create this.model
}
// this could be expanded with simple console menu with keyboard
// input and other console specific stuff
}
The fun part is how to do event handling. I implemented this by letting the view register itself to the controller using an interface, this is done using the Observer pattern (if you're using .NET you'd be using event handlers instead). Here is an example of a simple "document observer", that signals when the a document has been saved or loaded.
public interface DocumentObserver {
void onDocumentSave(DocModel saved);
void onDocumentLoad(DocModel loaded);
}
// in your controller you implement register/unregister methods
private List<DocumentObserver> observers;
// register observer in to the controller
public void addObserver(DocumentObserver o) {
this.observers.add(o);
}
// unregisters observer from the controller
public void removeObserver(DocumentObserver o) {
this.observers.remove(o);
}
public saveDoc() {
DocModel model = model.save();
for (DocumentObserver o : observers) {
o.onDocumentSave(model);
}
}
public loadDoc(String path) {
DocModel model = model.load(path);
for (DocumentObserver o : observers) {
o.onDocumentLoad(model);
}
}
That way, the view can properly update itself since it is subscribing to the document updates. All it has to do is to implement the DocumentObserver
interface:
public class DocumentWindow extends JWindow
implements DocView, DocumentObserver {
//... all swing stuff
public void onDocumentSave(DocModel saved) {
// No-op
}
public void onDocumentLoad(DocModel loaded) {
// do what you need with the loaded model to the
// swing components, or let the controller do it on
// the view interface
}
// ...
}
I hope these motivating examples gives you some ideas on how to do it yourself. However I strongly advise you to consider using frameworks in Java that do most of the things for you, or else you'll end up having a lot of boilerplate code that takes a long time to write. There are a couple of Rich Client Platforms (RCP) that you could use that implement some of the basic functionality that you most likely need, such as application-wide document handling and a lot of basic event handling.
There are a couple I can think of from my head: Eclipse and Netbeans RCP's.
You still need to develop controllers and models for yourself, but that's why you use an ORM. Example would be Hibernate.
IoC containers are cool'n all, but there are frameworks for this as well. Such as Spring (which also does data handling as well among other things).
I would argue that, in the case of AngularJS, utilizing callbacks is a code smell. I say this for the pure simple fact that AngularJS includes the notion of promises that you can leverage for the same use-case, and in fact look extremely similar in practice to the code you have already developed. Consider your test method:
var testMethod = function() {
return loadDoc();
};
function loadDoc(callback) {
// Create the XHR object to do GET to /data resource
var xhr = new XMLHttpRequest();
var dta,
// This is the deferred action
defer = $q.defer();
xhr.open("GET","url",true);
// register the event handler
xhr.addEventListener('load',function() {
if(xhr.status === 200) {
// We have a response, tell those who are waiting
// about it
defer.resolve(xhr.response);
}
}, false) ;
// perform the work
xhr.send();
return defer.promise;
}
The calling code could look like this:
testMethod().then(function(data) {
console.log('data', data);
});
Not only that, but the calling code could also look like this:
function callTheTestMethod() {
return testMethod().then(function(data) {
console.log('data', data);
return data;
});
}
And the code calling that code could look like this:
callTheTestMethod().then(function(data) {
console.log('I have data here, too', data);
});
Another benefit to using promises is that they already have a built-in way to handle failures:
if(xhr.status === 200) {
defer.resolve(xhr.response);
} else if(xhr.status > 400 && xhr.status < 600) {
defer.reject();
}
Just glancing at your code, it looks like your callback is actually being called twice, and to me looks like a bug. Also, on a side note, I don't think you should be instantiating your own version of XMLHttpRequest
here vs just using the Angular $http
service, but perhaps you have your reasons.
I would suggest this approach: Build an AngularJS service that uses the built in $http
service to abstract away your API Calls, something like this:
angular.module('my.module', [])
.service('TestService', [
'$http',
function($http) {
return {
testMethod: testMethod
};
function testMethod() {
// Just a simple example to illustrate private
// method usage..
var data = privateMethod();
return $http.get('url/' + data);
}
// Since this function isn't returned in the object above, only
// code that exists in this scope can actually reference it
function privateMethod() {
return '3';
}
}
])
;
You can consume this service in a controller (or directive, or another service) in this fashion:
angular.module('app.module', ['my.module'])
.controller('TestController', [
'TestService',
function(TestService) {
activate(); // Prepare for Angular 2.0!
function activate() {
TestService.testMethod().then(
function(data) {
console.log('I still have data', data);
},
function() {
console.log('something went wrong...');
}
);
}
}
])
;
Best Answer
You're on the right track. The controller is what binds the app together, so passing it around to the various view and model classes is fine.
However, don't think that just because you have a controller, all widgets must use that controller for everything. For instance, I wouldn't put the
onclick
method in the controller. The controller should be for routing business logic, but what you're doing is mostly specific to the GUI.I think a good general rule of thumb is that GUI events and callbacks should be within the GUI module. They then call the controller when interfacing with business logic. Fetching a string from the GUI itself is not business logic.
So, instead of:
I would do this:
With the above, the controller doesn't need to know what widgets are part of the GUI. All it does is provide an interface through which the GUI can request services.
If you prefer to keep the
on_click
in the controller, that's possible too, and not completely unreasonable. However, instead of building in knowledge of the GUI widgets into the controller, give the GUI an interface that the controller can use.For example, instead of doing
GUI.widget_entry.get()
, the controller would call an accessor such asGUI.get_entry_widget_value()
. That allows you to refactor the GUI without having to change the controller. If you move the entry tothe_widget_entry
instead ofwidget_entry
, the controller can be completely unaware and still function properly.