ModelController

Motivation

In some implementations, Model state may be small and only relevant
for a specific View, making the split between Model and Controller
classes excessive. Merging these classes leads to a ModelController,
which can be seen as a “Model with GUI intelligence”, or as a “Controller
with state”.

Compared to a traditional MVC solution, ModelController has the following
disadvantages:

  • As stated in the opening paragraph, it complicates handling of
    multiple Views
  • Potentially carries a dependency against the UI layer if,
    for example, needs to interpret pure UI events. This complicates
    its reuse outside of a UI application
  • It is generally less flexible, less reusable, and more complex to test for
    the reason give above.

The motivation for the first point is twofold: first, the Controller
part would have to differentiate which View is sending an event. Second,
different Views may have a different visual implementation and thus generate
different types of events. The ModelController interface would have to
accommodate nomenclature and behavior of all Views, leading to a bloated and
complex interface.

A ModelController approach is not necessarily wrong, but tends to become
brittle and carry excessive responsibility. It is therefore limited to
straightforward cases.

Design

ModelController hosts the logic to manipulate its internal state
in response to UI events, applying both consistency and business logic,
while at the same time handling events from the View:

  • The View dispatches UI events to the ModelController
  • The ModelController alters its state and notifies the View of changes
  • The View requests the new state from the ModelController and updates itself.


ModelController - 图1

Practical example

Trivially, a ModelController class holds state, instead of delegating it
to a Model object

  1. class ModelController(Model):
  2. def __init__(self):
  3. self._value = 0
  4. def button_pressed(self):
  5. self._value += 1
  6. self.notify_listeners()

This pattern can be observed, for example, in Enthought Traits

  1. from traits.api import *
  2. from traitsui.api import *
  3. class ModelController(HasStrictTraits):
  4. value = Int(0)
  5. increment_btn = Button()
  6. traits_view = View(
  7. Item("value"),
  8. UItem("increment_btn", label="Increment"),
  9. )
  10. def _increment_btn_fired(self):
  11. self.value += 1
  12. mc = ModelController()
  13. mc.configure_traits()

The above code, thanks to the Traits/TraitsUI framework, produces a window containing an editable text field
(showing the value zero) and a PushButton. Every time the button is pressed, the _increment_btn_fired handler
is called. By design of the class and the framework, the class is a ModelController because it holds
both Model state (value) and Controller duties (_increment_btn_fired).
Although invisible, validation logic is also performed in the ModelController by the Traits framework,
which rejects values in the text field not conforming to an integer.