Today, a basic topic: Model/View, the Desktop and TDD.
Some geeky respite, and enjoy. We got a lotta work waiting for us after this break.
Stay safe. Stay strong. Stay kind. Stay angry. Black lives matter. Demand different.
The basic idea behind all Model/View schemes, and there are several of them, is just this: draw a thick line between what your program does and how your program shows what it does.
In other words, it’s a compositional heuristic, guidance for how we break our problems into smaller problems. Give some objects/modules the responsibility for doing the work, and give other objects/modules the responsibility for showing the work.
Tho it was originally conceived in the smalltalk world, there are many modern usages of the idea. Nearly all UI-centric frameworks take the Model/View split as their starting point.
I want to approach this from the world of the desktop. I know, desktop apps don’t get much love today with our fetish for making everything a thin-client browser app. The MV ideas apply in the web world, too, but they’re usually simpler and more clear in desktop-land.
UI work is always a two-way path: on the input side, we have to gather user gestures and turn them into action. On the output side, we have to grab the results of that action and expose them to the user.
The two sides form a kind of circle. Get-a-gesture | Show-the-gesture’s-results. (We usually start by showing, actually, but when we do that we’re generally showing the results of an implicit "startup" gesture, one the user issued just by running your app in the first place.)
In modern UI’s, the gestures are varied. We’re allowed to do one thing, and then another, and then another. Some gestures are allowed only at certain times. Others can happen any time, including at the same time as other actions are going on.
The idea of the MV split is not a naturally occurring beast for most of us. In my experience, it’s a very rare developer who sees the value of that split right off the bat.
Instead, the budding geek grabs a UI framework, throws some stuff on the screen, gets extremely excited, makes it all interoperate as one thing, and gradually, over time, turns it into an unholy nightmare of hairball complexity that haunts her dreams for years to come.
Everybody laughs when I say that, but for many of us, that kind of thing was really our first foray into mind-blowing complexity. Command-line code is hierarchical, sequential, synchronous, and uni-directional. You literally re-draw the whole thing after every gesture.
Okay, back to it. In MV schemes, we have a View class, and it watches, through some mechanism or other, a Model class. We hook up the View to the Model, and now, when the Model changes its contents, the View more-or-less magically changes its rendering. That’s the "show" part.
The "get" part, where we get gestures from the user, is tricksier, and can be done in different ways, but the most standard way nowadays is this: the View is a shape on the screen that the O/S sends events to. Those events are user gestures.
The view turns those user gestures into code that eventually changes the model. The model-changes, in turn, are reflected back through the show process.
View changes model through one mechanism, which in turn changes the view through another mechanism.
In its simplest form — which almost never actually occurs — that’s all that the MV split is. It only sounds that simple because we’re purposefully ignoring a bunch of factors.
- One complexity is asynchrony. In a modern UI, the two mechanisms, in and out, are on different threads, often multiple different threads.
- Another complexity is object-orchestras. There isn’t one Model thing, there’s a bunch, arranged in a hieraarchy. And there isn’t one View thing, there’s a bunch, arranged in a hierarchy. To make matters more delicious, the hierarchies are rarely identical in shape.
- A third complexity is view-multiplicity. One of the great strengths of the MV split is that we can create two views that watch the same model and render it in different ways simultaneously.
- Still a fourth complexity is the dramatic array of possible configuration values for a view. Fancy UI views have font, color, size, shape, 3d-ness, highlighting, on and on and on. That’s a lot of stuff, and most of it can be varied independently from the rest of it.
- And a fifth complexity: the gestures from some views exist expressly to alter the behavior of other views. That is, they change each other, not just our naive understanding of the Model.
Add all this up and it’s a lot. So let me spiral back out of all this abstraction and tell you about using TDD to write desktop apps in the JavaFx (Kotlin & TornadoFx actually) environment.
The fundamental mechanism of JavaFx is an observer pattern. The controls on your screen, whether solo or in orchestra, are all observer’s. Most of their subjects are either individual property objects or various collections of property objects.
Individual traits of a control, it’s font or color, say, can be bound to different and separate properties. (They also are different and separate properties themselves, which allows us to swiftly return to hairball tangles if we’re not careful.)
Now we enter into my principles for using this thing appropriately and with TDD. I have very little idea how others work in this world. If I had to guess, I’d guess that they don’t use TDD or even broadly accepted design principles, instead relying on stack overflow snippets.
Principle #1: I use simple declarative data statements to get view watching property. I do GAK (geek-at-keyboard) testing to establish that the hookups worked. That is, I don’t TDD those connections at all.
My rationale: It is possible to test those connections, but the cost/benefit doesn’t justify it. Those tests are slow, hard to write, multi-threaded, flickery, and establish only two things 1) JavaFx works, and 2) my one-liner data declaration had the write fields in it.
Principle #2: I collect the properties into Model objects, usually one per composite View. When I construct the View, I give it its model. Part of a composite view’s construction is telling its subviews about the submodels they’ll watch.
Again, and for the same reason, there are no tests other than prima facie GAK tests involved in this.
Principle #3: I do not let Views watch or alter other View’s properties. If viewA has to change viewB’s behavior, it does so by changing a property in its own Model. That property will be observed by viewB, who will act accordingly.
This was a really hard principle for me to uncover, but it has enormously improved my productivity. View crosstalk is a particularly slinky implicit complexity. By putting view-controlling properties into Models, I gain explicit control and visibility for them. It helps a lot.
Principle #4: My Model classes aren’t domain classes. They are expressly outward-facing, towards the Views that watch them. A model’s job isn’t to be the domain object it represents, it’s to provide a surface for its Views that represents that domain object.
In fact, some Models don’t contain or reference any Domain objects. They’re there for, idunno, "view on view violence". 🙂 Some UI’s have truly dramatic transformations they’re capable of. They watch and control these domainless model objects.
Principle #5: I test the holy living crap out of my Model objects and their interactions, both internal and with the actual Domain (and Commands when I use Commands).
My model tests are true microtests. No threads, no graphics. They capture the UI logic, the relationships between fields, the impact of commands, and so on. This is the heart of me getting TDD levels of productivity in a desktop app development environment.
Principle #6: I use methods on the Model to implement most commands. Those commands both alter domain objects and alter the Model themselves. This is how I handle properties for the views that are actually computed from domain fields or other model properties.
A for instance, a list box with an up-arrow button to change the focus. I call the up() method on the model. It changes the focused object, but it also notices whether we’re at the top of the list and enables/disables the canUp property.
Principle #7: When multiple views have to do the same command, I extract that command from one of the models and make it work on both at the same time, tho it only does the actual domain work once.
This is annoying and tedious, but thankfully it’s pretty rare. I can often get away without having to do it at all, just by incorporating some of the properties from one model into the second model "as if" they were first-level fields. Doesn’t always work. Usually does.
So. Almost certainly, as soon as I finish this great baggy novel of a thread, I will think of another principle. But that’s enough for now.
My friend Ted recently started doing a Yahtzee app on his stream. Look up jitterted here and on twitch, he’s excellent. I won’t at him here, cuz I hate being volunteered to other people’s threads myself.
I felt double-dawg-dared to do the same thing. I won’t twitch mine, but I will live-code it and share the commentary reel. Mine will be a desktop app in the environment we’ve been talking about all along. It would help me if you would encourage me in this. 🙂
Desktop UI work is hard. Doing it so as to leverage what I’ve learned about TDD over the last twenty years took me a long time. I feel I’m still a work in progress. But now you know the principles I try to follow when I’m doing it.
Supporting The PawCast
If you love the GeePaw Podcast, consider a monthly donation to help keep the content flowing. Support GeePaw Here. You can also participate by sending in voice messages to be included in the podcasts. These can be questions, comments, prompts, etc. Submit A Voice Message Here.