I complained at the lack of a simple but reasonably complete framework for GUI applications based on the ModelViewController
concept, ascribing this lack partly to the need for such applications to present a seamless surface
to the user. I suggested that the main goals of such a framework should be to simplify the construction of application surfaces, and ensure the separation of surface and domain code.
As pointed out by contributors to MvcIsNotImplementable
, historically there has been greater clarity about models than about views or controllers. Accepting the need to segregate interface from domain code, we could try giving our framework a two tier architecture:
- a model tier containing domain data and associated logic, and
- a surface tier enabling user access to this data
While encouragingly simple, this approach poses difficulties of two sorts, the second sort harder to resolve than the first.
The first difficulty is that of abstraction. A GUI surface consists of an (often elaborate) arrangement of widgets managed via an event loop; there are well-known general patterns for arranging and managing widgets but the details are notoriously complicated and platform-dependent. The code of the domain tier, while probably not as complex, may be of quite a different sort entirely. It should not need detailed knowledge of how the surface works or indeed that there is a surface at all; nor should the surface need to know about the details of domain code.
The straightforward solution to this first difficulty is that user interface and domain code will be reliably insulated from each other if the surface and model tiers are redefined as abstraction tiers. The surface tier will then act as a facade hiding the details of GUI construction, while the model tier does the same for domain objects and logic.
Say a domain flag needs to be exposed in the surface for the user to view and set its state. A standard object could be created in the model tier that knew about the flag, could represent and set its state, and had an identifying title string. A peer for this object in the surface tier would create a checkbox widget, look up a suitable icon and localized string using the object's title, generate a suitable label widget and add both widgets to a panel. Acting as listener for events on the checkbox widget it could respond to them by setting the value of the model peer; if notified by the framework of a change in the peer it could set the state of the checkbox accordingly. (Of course checkbox widgets usually do have a built-in label; but a flag is a nice simple object to use as an example.)
While the surface peer would need detailed knowledge of its dependent widgets, it would need only general knowledge of its model peer; more powerfully still, it wouldn't care if the underlying domain flag was an object, a boolean primitive, an integer or a bit. The model peer would need to know in detail about the domain flag but possibly about nothing else, assuming the framework could arrange to notify the surface peer of any changes in the model peer's state. And the programmer would only need to hook up the model peer to the domain flag and specify the location of the surface peer in the GUI layout; the framework could reasonably be expected to deal with the remaining details of construction and maintenance.
None of this is very complicated compared to a JavaSwing
widget with its pluggable data model and UI delegates, or an JavaSwt
widget creating, maintaining and disposing a platform widget via the JavaNativeInterface
; it just extends further the principle of abstracting away detail behind a generalized facade.
The second kinds of difficulty presented by a simple two-tier architecture are not so easily resolved. The example above assumes a fixed, one-to-one relationship between the surface and the model peer; but except for the simplest of applications the relationship is actually both variable and one-to-many.
Firstly there is the targeting
relationship. In almost all applications the user will want to choose different objects in a viewer, switch between windows, open and close documents: generally to view and control different instances of the same domain type. Either a model peer must be capable of capturing multiple domain objects sequentially, or it must be possible to connect a surface peer to multiple model peers sequentially.
Then there is what might be called the multiplexing
relationship. A model peer may need to be simultaneously exposed in the surface by a number of different widgets, not necessarily even of the same type: our example flag could appear as a checkbox in a panel, as a menu item, as a toolbar button. Either the surface peer must create and maintain all these widgets, or there must be a number of surface peers for each model peer.
On the face of it, we could plump for the first solutions to both difficulties. For each underlying domain type a single model peer could be targeted at different instances of the type; this model peer would communicate with a single surface peer with multiplexed widget sets. However this approach depends on both solutions being workable, and each turns out on closer examination to have drawbacks.
The obvious problem is with multiplexing. As the surface will typically expose a model element using widget sets of different types (checkbox, menu item, toolbar button and so on), each widget set will most naturally be managed by a single surface element with knowledge of its type, which implies a separate object to handle multiplexing. This multiplexer could still be created in the surface tier, which however would then comprise both managing and multiplexing objects. Putting the multiplexing objects in another tier would have the advantage of restoring the original simplicity of the surface tier.
The domain elements to be targeted in the model tier would by definition be of the same type, but there would be clear benefits to creating a model element for each domain instance. This would imply a separate proxy to handle targeting; once again this could be created in the model tier, once again the model tier would be kept simpler if its targeting proxies were in another tier.
So on the one hand we need to create targeting proxies for model peers, while on the other each model peer needs somehow to be multiplexed to several surface peers. Our original model and surface tiers can be left as they are if we define an intermediate targeter
tier containing a set of targeting proxies, each capable of multiplexing a number of surface peers to its currently targeted model peer.
We now have an architecture for creating an application surface that can expose arbitrary domain objects to the user by means of an equally arbitrary arrangement of platform widgets. It enforces strict code separation by means of tiers communicating via standardized abstractions. What about the original complaint that MvcIsNotImplementable
? Our framework clearly implements the model, but it's not clear what has happened to the view and controller.
The answer (as pointed out when originally defining surface in MvcIsNotImplementable
) is that view and control are what a surface provides. Some surface elements provide only one of these: text status lines or progress bars provide only view, action buttons only control. Most elements, such as our example checkbox, will provide both the means to view a domain object and the means to control it.
Which finally pins down why MvcIsNotImplementable
. Those pretty triangular graphics we have all so earnestly pondered may conceal more than they reveal, because model, view and controller are not three objects but one and two halves:
Object newViewerController(int type)
To summarize: it should be possible to base a practical GUI application framework on the MVC concept, but indirectly. The basic architecture would comprise two main tiers acting as abstraction facades:
- a model tier for application objects wrapping domain data and logic
- a surface tier for platform widgets providing view and control of the model
Between them would be the critical mediating component:
- a targeter tier enabling each model element to be exposed by multiple surface elements simultaneously, and these elements to be attached to multiple model elements sequentially.
, this ModelTargeterSurface
architecture offers a way round the problem.
I use an "interactor" layer to address this problem. If the view corresponds to a pictorial drawing of an electronic component, the interactor corresponds to the schematic. The interactor is part of the model, and surfaces designed capabilities to the presentation. Here are some example interactor classes:
- OneOfManyInteractor? -- allows the user to choose one of many options. An example might be choosing a currency from a list.
- ManyOfManyInteractor? -- allows the user to choose many of many options. An example might be choosing keywords that apply to a given element.
- BooleanInteractor? -- the flag example above.
The choreography of a given user interface is therefore determined by the various interactors that comprise its surface. A presentation is then connected to whatever interactors that it is intended to display. For example, a OneOfManyInteractor?
might be rendered by a set of radio buttons, a single-select dropdown list, or a menu.
The role of the presentation layer, then, is to collect user gestures and map them onto whatever protocol is supported by the interactors, and to correspondingly present a suitable rendering of the interactors to the user. The role of the interactor layer is map the interactions with the presentation layer onto corresponding changes to the model.
Some contributors have also used the term "mutator" instead of "interactor".
pattern is often useful for joining the presentation and interactor layers.
Finally, it might be worth noting that this approach also allows non-graphical presentation layers to be supported, including plain-text, voice- and telephone-driven, and so on.
At the detail level this is certainly familiar - I have a Selector
model element that has the same range of surface peers as your OneOfManyInteractor?
. I am not so clear how you achieve multiplexing (connecting several surface elements to one model element) and selection (switching connections between models). - DavidWright
I don't know what you mean by "range of surface peers" or "surface element", can you try different words? Specifically, can you describe the visual elements you are connecting to a model (or models), and then describe the behavior that a multiplexer or demultiplexer should have in that context? The definition of a MultiplexerInteractor and DemultiplexerInteractor probably isn't so hard, I just don't see it's application in this context.
Do your interactors map to a domain object eg flag, or to a surface element eg checkbox, menu item, button? In the second case can you explain how
- view consistency is maintained amongst interactors exposing the same domain object
- interactors are notified that they should expose a different domain object
I have taken your suggestion to use different words and substantially reworked the opening text. The following glossary may also help:
- Abstract user interface defined in terms of platform-independent surface elements that also hide low-level construction detail such as (in the case of a GUI surface) panel layout, labelling, icons.
- surface element
- Creates and manages possibly just a single widget such as a checkbox or menu item; or a panel containing eg a combo box and title label; or even a group of logically related items such as a pair of Undo/Redo buttons. Has a peer relationship with a corresponding element in the model tier, mediated by a proxy in the targeter tier.
- A surface element and the model element which it exposes (ie for which it provides view and control) are peers of each other. A single model peer such as the flag described above may be multiplexed via its targeter proxy so to several different surface peers: for instance one managing a toolbar button, another a menu item, a third a checkbox in a dialog.
- Exposing a single model peer by means of several surface peers simultaneously; all the surface peers can update the model peer and be themselves updated when its state changes.
- Using a proxy to expose different model peers using the same group of surface peers.
The last two concepts are rather like pointers in C: simple and powerful but hard to get your head round. To give (just about) practical examples of how they could be implemented within a well-known API, multiplexing could be achieved in JavaSwing
by setting the same pluggable data model to several components at once. You could then do targeting by exchanging the data model plugged into each component, providing you first found out which components the current model is plugged into. However you would also end up with your surface and model code much too tightly coupled for comfort, which is where something like ModelTargeterSurface
would be handy.
The next question is how to turn this general architecture into something specific enough to address my original complaint that MvcIsNotImplementable
. - DavidWright
This page proposes a redistribution of responsibilities among the kinds of objects used to implement graphical applications. It applies to applications with sufficient complexity that the simple pairing of graphical element to computational element breaks down. The breakdown is addressed by targeting
in a middle tier. I understand this tier to serve the role of the GOF Mediator, but perhaps not so monolithically. I would love to see an example. Unfortunately, any simple example will probably not need the middle tier. So I will here suggest an example that could be worked up to sufficient complexity to show both targeting and multiplexing without distracting the reader with an unfamiliar domain. That example is the LittleSimulator
. The specification is unspecific enough that it easily bends to the needs of a demonstration program, while the domain has multiple models, multiple views and asynchronous behavior if that is required to illustrate the power of targeting and multiplexing. The problem is also compelling for anyone who has waited for an elevator. I've coded the LittleSimulatorInCocoa
which was satisfying, but neither open ended nor portable. I suspect that ModelTargeterSurface
could address both of these issues while preserving the simplicity of my cocoa implementation. -- WardCunningham
One reason for starting these pages was to explore and get input on whether I have stumbled on a solution to a common if poorly-defined problem, and it's nice to get both the input and this positive feedback. For now at least, I am too preoccupied on the one hand with documenting the broad mechanics of ModelTargeterSurface
) and on the other with detail design issues such as multiple views (see recent discussion in MvcIsNotImplementable
) to take up Ward's interesting suggestion.
Perhaps some keen coder could have a go based on SuperficialMts
once it gets down to detail. If so, this would be especially pleasing to someone whose day job is writing documentation. -- DavidWright
Having now looked at LittleSimulatorInJava
, it doesn't actually seem to be quite the ticket: needs too much domain design, objects do not have a wide range of properties. But it does point up the need for a SpikeSolution
to demonstrating ModelTargeterSurface
now includes one - will someone take up the coding challenge?
[One year later] No one did, so I did it myself as introduced at http://superficial.sourceforge.net
Suggested elevator model refactored to LittleSimulatorInJava
as it seems to belong there DavidWright
I am skeptical that this architecture will work cleanly in client/server applications where presentation logic is divided between the client and the server. I don't know whether this stems from a flaw or underspecification of the architecture, my undercomprehension of it, or both.
The example I have in mind is a web application that supports, e.g., paging of large lists of data and setting/unsetting the state of controls on a page in response to user selections. In the first example, pages are usually "chunked" by the server before being sent to the client, implying that at least some of the targeting layer (or is that the surface?) resides on the server side. In the second example, all the information required to update the state of the UI is available on the client side, so it would be desirable to do all the work of updating state without connecting to the server. Hence, part of the targeting layer wants to live on the client side.
Put more broadly, one would like for the network to exist "between" layers in the architecture - the client side contains the surface, the server the model, the targeter on one side or the other, and network communication is envisioned as communication between the surface and targeter (or targeter and model) layers. But the responsibilities of the target (and possibly surface, depending on where some of these responsibilities reside) seem to straddle the network connection. That adds complexity to the layer thus afflicted, as its state is essentially split.
One way of coping with this might lay in treating an HTTP request/response pair like an atomic transaction. The "true" targeter and model live on the server, the "true" surface lives on the client. However, between requests, the client works with a "mini-model" that contains proposed changes to the actual model, and a "mini-targeter" that mediates those changes and their effects on the state of the surface. When the user chooses to "commit the transaction," an HTTP request is made, the transaction is sent to the server (as a series of changes to the model), and the results of the transaction are returned to the server, along with a new "mini-model" and "mini-targeter".
Alternatively, perhaps the targeter lives entirely on the client-side, aggregates "pre-commit" changes to the model, and handles communication with the model to commit the transaction and update state permanently. I like this for Web transactions because the client side of the connection is wholly owned by the targeter, and the server side wholly by the model. Security considerations like authentication and authorization seem more properly to belong in the model, as well, and since they are also best placed on the server, that works out well. On the other hand, does the targeter have to know too much about the model (i.e., a change to X, mapped to the surface as a select box, results in a corresponding change to Y, mapped as a checkbox) at that point?
Input validation frequently takes place on both the client and server sides. What layer's business is that, or is it a cross-cutting concern? I could see the surface doing syntactic validation according to type, say, and the model doing more semantic validation related to business logic.
I hope you find this feedback useful. -- PhilGroce
I do indeed. Please bear in mind that MTS itself only claims to address issues around implementing the MVC paradigm for GUI surfaces (which could be doing client-side processing in a web application); in particular, the specifics of the targeter tier are closely tied to such issues. However the underlying principles could probably applied more generally:
- The goal of any interactive application is to enable the user to view and modify domain data as transparently as possible.
- An application may usefully be be regarded as comprising a model which contains objects representing domain data and logic, and a surface which provides the user with view and control over the model.
- Coupling between model and surface will be minimized by hiding them behind abstraction layers that communicate via the most general possible protocols.
- In all but trivial applications some sort of mediating tier will be needed to manage communication between model and surface.
Based on these principles one can ask useful questions about design problems such as those posed by GUI applications or by your examples:
- What is my model?
- MTS answer: Objects that abstract further objects representing domain data and logic.
- Example 1: Server objects that abstract server domain objects.
- Example 2: Client objects that either abstract server domain objects or (preferably) act as proxies for such abstractions on the server.
- What is my surface?
- MTS answer: Objects that abstract platform objects enabling user interaction.
- Example 1: A server proxy for an MTS surface on the client.
- Example 2: An MTS surface on the client.
- What mediation is required between the two?
- MTS answer: Allow surface objects to target multiple instances of the same model type sequentially; allow model objects to be exposed by multiple surface objects simultaneously; ensure the surface always correctly represents the currently targeted model.
- Example 1: Something like an MTS targeter on the server, communicating with the server proxy surface.
- Example 2: An MTS targeter on the client.
If the answers for your examples are anything like right, the somewhat surprising conclusion is that
- a variant of MTS could perhaps be used for web applications
- server-client communication could be contained within either the model or the surface tiers
- the mediating tier could reside either on client or server and need not be split
This last point seems a good idea to me as someone who has found mediator code tricky to write for a single memory space, let alone across a stateless protocol.
Perhaps it's not so surprising that MTS seems to be applicable to web applications. They are just GUI applications as far as the user is concerned, it's only the poor programmer that has to grapple with managing the split in either model or surface. MTS may help to define the problem in general terms susceptible to a once-for-all solution.