Okay, let's take a step back. User
is data, in this context, data should be managed and maintained by the model layer.
The controller is responsible for coordinating actions and notifications between the view and the model. In this sense, when the user triggers a request in the view (ie to perform a sign up operation), the view triggers an event, which the controller subscribes to. The controller obtains the required information from the view and passes that information to the model. This might then work backwards, the model triggers an event based on the success of the task and the controller would notify the view ... as required
The important part of all this, is no one part knows how the other part actually works ... and doesn't care
Let's start with the basics. You need some way to represent a "user"
public interface User {
public String getName();
}
Now, you need someway to create a User
...
public interface UserFactoryListener {
public void userCreated(User user);
public void userCreationFailed(Exception exp); // Or some other error object
}
public interface UserFactory {
public void makeUser(String name);
public void addUserFactoryListener(UserFactoryListener listener);
public void removeUserFactoryListener(UserFactoryListener listener);
}
So, this is a basic concept of a factory which will take the name of the user and create a new User
object, the important thing to note here, is we don't care "how" that is done, only that is done and that it will generate either a success or failure event
But, what if we also want the ability to authenticate a user?
public interface UserAuthenticationistener {
public void userAuthenticated(User user);
public void userAuthenticationFailed(Exception exp); // Or some other error object
}
public interface UserAuthenticator {
public void authenticateUser(String name, char[] password) throws SecurityException;
public void addUserAuthenticator(UserAuthenticator listener);
public void removeUserAuthenticator(UserAuthenticator listener);
}
So, this is the same basic idea, it takes a name and password and authenticates the user in someway, which we don't care about, and will generate a success or failure event which we can subscribe to.
"Yes, but what about the user model?" I hear you say, glad you asked!
public interface UserSigninModel extends UserFactory, UserAuthenticator {
}
Well, okay, this is the sign in model, but it incapsulates the two primary functions we want to support, sign up or login.
Now, we need some way to coordinate the two, this is the job of the controller...
public interface UserSigninController extends UserSigninViewListener, UserFactoryListener, UserAuthenticationistener {
public UserSigninModel getModel();
public UserSigninView getView();
}
... well, that was kind of underwhelming, but that's kind of the point
Finally, we need some way to interact with the actual user, the view!
public interface UserSigninViewListener {
public void signupUser(UserSigninView view);
public void authenticateUser(UserSigninView view);
}
public interface UserSigninView {
public Pane getView();
public String getName();
public char[] getPassword();
public void addUserSigninViewListener(UserSigninViewListener listener);
public void removeUserSigninViewListener(UserSigninViewListener listener);
public void userSignupFailed(Exception exp);
public void userAuthenticationFailed(Exception exp);
}
So, all of this does one simple job. It describes the intent (or the contracts) that each layer is expected to provide. At no point does any one part actually care how the other part is implemented, only that the contract is upheld
For example, a possible controller implementation might look something like...
public class DefaultUserSigninController implements UserSigninController {
private UserSigninModel model;
private UserSigninView view;
public DefaultUserSigninController(UserSigninModel model, UserSigninView view) {
this.model = model;
this.view = view;
}
@Override
public UserSigninModel getModel() {
return model;
}
@Override
public UserSigninView getView() {
return view;
}
@Override
public void signupUser(UserSigninView view) {
getModel().makeUser(view.getName());
}
@Override
public void authenticateUser(UserSigninView view) {
getModel().authenticateUser(view.getName(), view.getPassword());
}
@Override
public void userCreated(User user) {
// Coordinate with the navigation controller to move to the
// next part of the program. This might pass the User object
// to the navigation controller (as a event) so it
// can be seeded into the next model
}
@Override
public void userCreationFailed(Exception exp) {
getView().userSignupFailed(exp);
}
@Override
public void userAuthenticated(User user) {
// Coordinate with the navigation controller to move to the
// next part of the program. This might pass the User object
// to the navigation controller (as a event) so it
// can be seeded into the next model
}
@Override
public void userAuthenticationFailed(Exception exp) {
getView().userAuthenticationFailed(exp);
}
}
The controller itself isn't doing a lot, other then coordinate the communications between the view and the model. It also provides the possibility to provide notifications to another "navigation controller" which might be responsible for determining which controller/view should be shown next. It might even create the "base" model wrapping the new User
object in it.
Disclaimer
This is an overly simplified example, intended to provide a demonstrable concept of the work flow - the point been, the "how" is unimportant, as it should all be hidden behind the interface
, what's important is the contract that each layer agrees to implement.
Also, my JavaFX is minimal, so the event management could be changed to better suit how the API actually implements it's observable pattern