I wasn’t sure what to call the following architectures:
but since they’re all some kind of model/view abstraction, let’s just wing with that. They’re all suffering from the same problem. They’re pointless.
/--- View <----\
interact | | update
v |
Controller -------> Model
modify
the View propagates user interaction to the Controller, which then updates the data stored in the Model, which then updates the data displayed in the View. Which sounds great since you can keep your UI code completely separate from the business logic present in the Controller, which also does not deal with any database-centric code. The thing is, while this looks great on paper, it does not really net you any benefit. You’ll have discussions regarding if some logic belongs in the View or in the Controller, different schools of thought might place one in one and one in the other. The Model needs extensive knowledge about the View for it to update the correct fields. The controller must parse the specific inputs from the View and do specific things with the Model. Despite how decoupled this looks, they are all strongly coupled and a Controller for one component cannot be reused for anything else. You are essentially splitting up 1 unit into 3 units, which requires extra boilerplate for them to be able to communicate with each other, but you are not getting any benefits with decoupling anything.
One advantage usually touted is that it helps with unit testing. Usually, the only part that has logic that is extensive enough to make sense to unit test is the Controller, and the rest more or less has logic as basic as “change the text in a field to this” and “put this in the database”, the latter which probably calls something else in your code that would probably be tested itself, so the architecture does not lend you any benefits in the end.
input update
-------- -------
/ \ / \
View Presenter Model
\ / \ /
-------- -------
update update
A similar but more linear take. In this case, the View and the Model are fully decoupled. Any actions you take in the View is handled by the Presenter, which in turn takes the responsibility of updating and retrieving values from the Model and then update what’s displayed in the View. This might look similar, but now the Model has no concept of what a UI is - it’s merely communicating raw values that the Presenter puts where it should be. This solves the issue of the Model being tightly coupled to how we want to display the UI, but raises another - Presenter is now taken on this extra burden and runs the risk of becoming a god class. Similarly to MVP, none of these components are reusable or exchangeable.
update
------
data binding / \
View <------------> ViewModel Model
\ /
------
update
The difference here is that you use something called Data Binding in the connection between the View and the ViewModel. A property that is “data binded” (data bound?) is one that gets a callback whenever it has been updated, by some listener or observer. Thus, the View layer would declare that “here I will display someones #NAME”, and once that becomes updated in the ViewModel, the change automatically carries over, so there is no need to be aware of anything UI related in the ViewModel, unlike the MVP Presenter or the MVC Model. The View is now the sole component that knows anything about your UI. It’s up to the View to bind any input-boxes or buttons to commands or event handlers in the ViewModel, that when prompted executes any required logic.
A problem with this though is that MVVM components can be more difficult to debug when your bindings fail, and despite dividing up into separate components, they are still tightly coupled anyway. The ViewModel still runs the risk of becoming a god class. The asynchronous relationship between UI interaction and response might make automatic testing difficult to implement. You will also drown in boilerplate unless you tie yourself up with a 3rd party dependency, such as a framework, which will then infect your entire codebase. Plus, if it’s easy to use, it means it hides away a lot of the boilerplate code, where bugs may hide.
So this is where you might expect me to show off my own, better idea for an abstraction that solves all the above problems. If that’s the case, then I wrote everything so far all wrong. There is no end-all abstraction that just works for everything and that’s the entire point. In the case of an Android application that perhaps displays a list fetched from a REST API, the design might look like this:
REST API
^ |
| v
App ---> Background
UI <--- service
where the UI prompts some kind of background service to make a call to the server (because you don’t want to do this in the UI layer) which upon receiving the data, updates the UI. This might look like MVC on a high level. Imagine, if you then apply MVC/MVP/MVVM classes in the UI layer that interact with the background service, which means you are essentially fractally nesting the abstraction into itself. This could go further with the background service storing data into a local database and then having abstractions on top of that. You might have abstractions for communicating with the rest API as well.
One advantage people tout for using an abstraction is that you’re more flexible to keep up with change. But where is that change coming from? Currently I’m working with an application that has been maintained for 12 years. This app has vestiges from all “best practices” over the years it’s been in development, but what the app has actually been used for, the “business case” has always remained the same. We have some basic Android activities here and there, some fragments here and there, some ViewModels here and there, some LiveData here and there… and whatever is the flavor of the year, will most likely make its way in. Guess what parts of the app are the least of a pain in the ass to debug and make changes to? It’s the most, dead simple, straight up, Activity
that “hard code” in functionality without spreading it across abstractions. This is even code that can look relatively the same as it’s always done since Android Froyo.
That’s also where MVC advocates step in saying that once your complexity goes up, you’ll need to implement MVC. I disagree. An activity is essentially “one screen” in your application. If you got way too much shit going on in “one screen”, you are probably also having UX problems on top of your architectural ones. Too simple, you don’t need MVC. Too complex, MVC doesn’t solve it anyway, because you are just tripling your complexity, while obfuscating what goes where. Changes to any component propagates into the rest. Meaningful tests need to test them all together. You don’t actually want MVC/MVP/MVVM/Flavor-of-the-year in your program.