I've developed my own pattern for the UI presentation. It's evolved from MVVM[1]. Basically; I was implementing MVVM, found some aspects not to my taste; and changed it into something I like. The result of this is a composite pattern I'm calling the View Bridge Mediator (VBM). Very creative name...
The VBM has evolved from the MVVM, which evolved from MVC[1:1][1:2], who's evolution can probably be traced to the primordial binary.
One of the evolutions, which MVC could be contrived to support, but wasn't explicit, is that each role is a Layer[1:3]. Simplest implementation will have a single class per layer; but as soon as the responsibilities expand, the class count should expand. This has been most prevalent in the Mediator layer dealing with n-tier system. It becomes a mediator composite of mediators. There is still a single Mediator that communicates to a single Bridge, but from there; the layer should expand as required to follow best practices; like Single Responsibility[1:4].
What it Is
VBM considers 3 roles.
The View, which understands the visual components of the UI. It doesn't contain any logic; it just understands how to return the ui component for a specific purpose. View changes invoke methods on the Bridge.
The Mediator is the interface to the rest of the app. This is intended to follow the Mediator[1:5] pattern; hence the name. This knows about how to get data, how to talk to other components in the app. It contains business logic, and transforms data into a form that can be invoked without branching. For a UI; if there's a network request, the Mediator will handle translating the specific error message into the friendly message the user will see. When app level changes propagate to the Mediator; it invokes the Bridge to perform updates to the View.
As more complex scenarios develop; it is expected the additional classes in the Mediator layer will handle the translations and propagate them up.
The Bridge is the glue between the View and the Mediator. As the name suggests, it follows very closely the Bridge[1:6] pattern, but it does run into "Bridge Implementation Issue"[1:7] of there being only one implementor. The Bridge in VBM knows what controls to ask the View for and what data/strategy/state/etc from the Mediator to ask for. It knows how to correctly combine them. Beyond that; the Bridge should do so very little.
Sequence Diagrams
This diagram shows a simple method call sequence of an VBM that's refreshing user data. The flow of methods calls goes both directions between each layer. The Mediator is the only layer calling into the app and the only layer the app can communicate to. In the case of Rx (or similar, eg: Event Bus); Observables from the app should not be passed deeper into the VBM.
This also applies the approach of fully encapsulating the "data". This requires passing in the ui-widget to set the data to. I don't like this here due to the lack of partial class support in Java. If Java had partial classes; I could include a partial class as part of the UI layer and it'd be wonderful.
Here's a sequence diagram that has removed passing the ui and utilizing Collecting Parameter[1:8] to get the string (general case) representations back.
The strength here is that the data model doesn't know about UI widgets. In Java; a StringBuilder
could be used. This would be a good option for a Collecting Parameter.
How It Works
The VBM has a major change from both the MVVM and MVC patterns; there's no model. Both of these patterns treat the model as a Domain Model[1:9]. The VBM doesn't build a singular representation of the data for the View/Presentation. The Mediator gets all of the data (possibly Domain Models) that are required for the View to be populated. The Bridge knows which model method (thinking pure OO) maps to what UI control.
This shifts updates from happening in the model which requires updates to the UI; to updates in the Mediator (which could be wholesale new classes) and updates in the Bridge. The UI should be as decoupled as possible from any supporting data changes. If the "Member" class becomes the "Employee" class; and the presentation does not change - Then the View should not change.
This is heavily influenced by my practice of isolating changes to as few and relevant places as possible. If the presentation isn't going to be different; then it shouldn't need a change due to a different Domain Model being used.
I'm going to re-phrase Fowler[1:10] regarding MVC for the VBM
Fundamentally the presentation and mediator are about different concerns. When you're developing the view you're thinking about how to layout a good UI. What controls are required, how they're placed. When you're working with the mediator; you're thinking about business policies, where to retrieve data from, and what other parts of the app are dependencies.
Very similarly to the ability of MV[C|VM] to swap out the View component for different presentations; VBM also allows changing the view and the mechanisms behind the scene remain unchanged. With VBM; the Bridge will (likely) also need to be updated for the new control types returned from the View. This is the purpose of the Bridge; to be a dumb pipe that allows decoupling of the brains (Mediator) from the face (View).
The non-visual components are going to be far easier to test than the visual. With this structure; using a FakeView that instantiates a Bridge allows full testing from the Bridge down. The View being so simple defies meaningful tests. Any tests on the View should amount to testing the language, or framework; which are invalid tests.
Automated UI tests shift from testing function to testing appearance on multitude of devices. Automated ui frameworks that take screen shots can provide information for a human to validate easily for localization. Shifting the focus from testing both appearance and localization as well as behavior helps streamline what the Automated UI tests should be. Not that there can't be validation that the behavior is correct; mostly that the right control is provided - It's less required given the behavior can be tested via unit and integration tests.
When to use it
I find the value of this separation anytime there's any behavior around how the UI displays data, or responds to user interaction.
If not this particular pattern; you'll want to have some pattern in place separating the presentation from logic. Otherwise you'll be in a world of hurt when the UI and Logic start to evolve. Just think about ASP.NET and early PHP days... You want a pattern in place.
Additional Thoughts
As this pattern and post was spawned by a write up of my version of MVVM, I had some thoughts about what delineations should be applied to the layers. I'm migrating those thoughts to this post.
I look at VBM as a logical collection of classes that are dedicated to showing a specific UI. This results in a few strong guidelines I have when I've been implementing VBM.
-
No reusable code.
The original application of this was that my Model layer in MVVM wasn't reusable. With the Mediator layer; this isn't as problematic. The data representation has been moved to Domain Models outside the VBM.
This still applies that any code that is in a VBM layer should not be considered reusable. It has one purpose; and it's not general. -
No logic in the View
The View is responsible for knowing what control displays what data. Not how to display it; or how to change the control - Just which controls display what information. The View should be a collection of methods that return UiControls. These are used by the Bridge to combine the data made available from the Mediator. -
Only the Mediator communicates outside the VBM
There's no other layer that makes sense to perform any logic or action that extends beyond the VBM layers. With the Mediator layer closest to the app; it takes on the responsibility of communicating. This, of course, ignores the View taking user inputs. If there's data to be retrieved, the Mediator does it. If there's transitioning to a new screen to happen; Mediator does it. Showing a new view can be a little odd, as some platforms have requirements that make it simpler to do in certain fashions. Android's preference for using an activity to start a new activity is one example. I'm fine with a View/Activity reference getting passed to the Mediator to perform an operation. Even getting passed outside the VBM as a base class (Activity in Android) is acceptable. There should be no dependencies on any VBM classes outside of the VBM; this includes any Mediator layer classes. The Mediator layer can know about the rest of the app; the rest of the app doesn't know about it.
Databinder
In presentation layers; DataBinding[1:11] often comes up. Looking at the code from a pure OO perspective; there's no place for databinding in the View. Google has put out a library[1:12] to do databinding for Android... I cringe at the thought of how that's going to become a painful part of the product down the road. Using data binding will cause the product to be harder to test and become more fragile as changes are required.
How data binders are allowed to function; or are required to function cause a lot of concern for me.
-
Logic in the View layer.
It becomes too easy to start including logic in the View layer. Not just the View's 'code-behind' layer, but into the view representation. In the case of Android; logic would be in the xml file. Having logic in the UI definition is about as close to the highest offense I'll find with code. I'll debate quite extensively to avoid putting logic into the UI. -
Removes Abstraction
It readily violates the layer boundaries of the VBM. Passing a data object into the highest level of the UI means that it is then tightly coupled to that representation of the data. There is no abstraction; there is no decoupling, there's no data->ui bridging. The value VBM is intended to provide - *poof* - when data binding is implemented. -
Encourages violating encapsulation
UI databinding normally doesn't allow a large feature set in what is written to bind data. And if it does; why the hell are you writing code in the UI?!?!?! ... ahem... Since there's not a lot of codability with the databinder, it encourages violating encapsulation. You'll need to use getters (I'm working on a post about object oriented programming encapsulation; where getters are bad) or just expose the naked values. It corrupts the encapsulation of the Domain Models, or forces new data classes just for the VBM. Writing data objects just for the UI to consume is going to turn the VBM into a badly organized MVC.
If you want to go the databinding route; I think the MVC pattern is going to be more supportive of those efforts.
Show Me The Code
I have sample code applying this pattern that I originally wrote as a model for my interpretation of MVVM. A quick rename and BAM! hehe
First we look at a stripped down version of the View class.
This shows both returning the view control and taking in text to set the value.
I have my preference, but gotta flex for now. This should be pretty straight forward; no logic; nice and simple.
Another approach to this to maintain encapsulation is to utilize an interface on the view of the methods the Bridge will know about.
Speaking of the Bridge
The MainBridge constructor could take a MainView
interface instead of the MainActivity itself. This gives a little better encapsulation to avoid cheating like having the Bridge call findViewById
.
The primary example out of the Bridge gist is the renderSimpleApiResponse
. It demonstrates the approach from both of the above sequence diagrams. While I dislike the SimpleApiResponse
having reference to a ui widget, code is cleaner.
No; I don't support a getWelcomeMessage
method. It's not hugely objectionable; but it's still returning a naked value. Utilizing the Collecting Parameter allows the object to never expose a naked value.
There's no question of formatting or organization - the value is written as the object wants it written.
Finally the Mediator
This uses the RxJava Observables for async behavior. I like it, but there's some concern over memory issues and strong references on the team at work. Consider this placeholder for any kind of async functionality; where the 'call back' invokes mainBridge::renderSimpleApiResponse
.
This Mediator isn't doing a lot; I grant that. This is the layer that would also be responsible for shared preferences, launching other activities, and other app/system interactions. If these get broken into multiple classes; all the better. Shared preferences, for example, would be in a SharedPreferences
extending class. The data retrieval might be in a DataAccumulator
class. This would allow the Mediator class to be a mediator and know about all the other bits. Plus single responsibility, and those other pesky best practices.
Conclusion
The View Bridge Mediator (as I'm calling it) is a pattern for a presentation that's attempts to be explicit in the expectations of each layer. It follows the lines I've found most valuable while working on and testing a ui.
This does not demonstrate other aspects I feel are very important to making the ui pattern extensible; which includes a Strategy Pattern[1:13] for variation in behavior. I hope to have an example of that in a later update to my sample project.
There can be a lot of different implementations of the communication between these layers. If I had to give a succinct description of each layer:
- View : Handles screen UI widgets
- Bridge: Combines Mediator's data & functionality with View's widgets
- Mediator: Knows how to interact with the app for the screen
Footnotes