Michael Comella wrote on 11.04.2016 22:44:
Experience with these architectures on other platforms (e.g. MVC in
web dev) would also be useful to hear! :)
I've been using strict MVC in XUL extensions and web apps. The way I use
it is:
* Model
contains the logic, that is all abstract functionality. Most
important rules are:
1. model must never ever access the UI, under any circumstances.
Decoupling happens via subject/observer design pattern or simple
callbacks. E.g. all async model functions have a successCallback
and errorCallback, which gets you a long way in decoupling.
Callbacks should be prefered over generic observers, because
they are more specific and more controllable and create less
accidental cross-pollution.
2. The model never initiates anything, all action is triggered by
the UI. If there's some polling or push needed, then the UI
requests it from the model and passes in a callback, the model
is running the poll/push, and notifies the UI via callbacks.
3. All code that can be cleanly put into the model (without
violating rule 1 or 2) should be put there.
4. I usually implement these as separate JavaScript files. It is
easy to check that nothing in that files accesses the View or
directly the Controller.
* View
contains the UI code. In XUL and web apps, that's XUL or HTML. It
contains the structure of the windows.
o Style is the looks, e.g. colors, margins, icons etc.. For web
apps, that's in CSS - and style should not change the HTML, only
CSS. In MVC, that's part of the View, but it's nice that HTML
separates this.
* Controller
hooks up View with Model. In web apps, that's the JavaScript hooked
up with the HTML. The controller
o has the onload handlers that do the startup and and triggers the
init of the app by calling the model init functions,
o populates the View by calling the model,
o it hooks up event handlers that call the appropriate model
functions etc.
o sets up poll/push listeners and hooks up their result to the UI.
* Persistence
o I usually implement persistence as part of the model, but
encapsulated in library classes.
o Persistence is often not a single method and thus a single
layer, but storage happens several places: Sometimes files on
disk, sometimes as preference, sometimes JSON, sometimes sqlite,
sometimes on the server using REST+JSON calls.
o I separate out the storage bits and bolts into a storage class,
e.g. I have a high-level myPref class where I can just say
myPref.get("bookmarks.autostore", false); or similar.
The above is my understand of the original MVC design, as defined by the
GoF and others around that time. Since then, Microsoft and others have
redefined it, and let the Controller or a Manager contain all the logic
and the Model contains only data fields, no logic functions. However, my
understanding of the OO idea is that objects contain both data and
functions that operate on that data, this is why I chose the above
approach of putting pure logic into the model.
A few other coding rules applying to all layers:
*
I try to make the code as dense as possible, that the code lines
only show what I really try to accomplish and don't have a lot of
technical bits and bolts.
*
Error handling
o Exceptions and errorCalbacks
o The function where the error appears first is responsible for
creating a user-readable string for it. Other, higher-level
functions only wrap it when they can add substantially important
information for the end user.
o Errors are generally passed up unchanged, up to a level that
knows what to do with it. Either it can recover, then the error
is handled on that layer where it can implement an alternative
fallback. Or the error is shown to the user, then it's handled
at the controller level which triggered the whole action. This
allows me to have a different error UI depending on whether I'm
in a main window (e.g. as error popup), dialog (e.g. error
message shown inline), or in an automatic function (e.g. errors
are startup not shown, but the feature is deactivated).
Example:
There might be an Account object that can read the username (and
possibly password) from a preference, and has a function
login(successCallback, errorCallback) that contacts a server. If the
server accepted the credentials, successCallback is called, otherwise
errorCallback(ex) with a specific reason (as error code and
user-readable string) of why login failed. The Controller, on startup,
checks with the Account object whether we have any stored credentials,
and if so, asks to login() directly at startup. If it fails, it doesn't
show an error, to not both the user, but we'll simply be logged out. It
it succeeds, the successCallback that the Controller passed in then
updates the UI to show the logged-in state. Or alternatively a generic
"logged-in" observer that the Controller had hooked up between Model and
View does that. If we're not logged in and the user clicks to log in,
the Controller asks the View to show the login UI. The View returns the
values, and the Controller passes them to Account and calls login() and
updates the View. If now the errorCallback is called, then the
Controller will call a View function to show the error. Or better yet,
the login dialog shows the error inline.
_______________________________________________
mobile-firefox-dev mailing list
mobile-firefox-dev@mozilla.org
https://mail.mozilla.org/listinfo/mobile-firefox-dev