Information Models, Views, and Controllers

Smalltalk's Model-View-Controller architecture was built with user interface design in mind.


July 01, 1990
URL:http://www.drdobbs.com/windows/information-models-views-and-controllers/184408380

Figure 2

Figure 1

JUL90: INFORMATION MODELS, VIEWS, AND CONTROLLERS

INFORMATION MODELS, VIEWS, AND CONTROLLERS

The key to reusability in Smalltalk-80 lies within MVC

Adele Goldberg

Adele is the president and CEO of ParcPlace Systems. She can be reached at 1550 Plymouth Street, Mountain View, CA 94043.


We have all experienced the benefits of reuse in every aspect of life. For example, when traffic control signs are reused throughout a city, a state, and even internationally, our ability to navigate streets and highways is helped. Reuse of text editors and picture editors across applications on the same or different hardware systems improves our ability to use those applications. The value of a graphing or diagramming technique increases with the number of ways it can be reused on different data, thereby improving our ability to quickly analyze new kinds of data. Reuse benefits the user as well as the application developer.

In the context of software applications, to reuse a software component generally means keeping it the same, while changing the context or style in which it is used. Text, for example, might stay the same, while the fonts and document layout containing that text might vary; data might stay the same, but it might be presented differently in terms of graphs, pie charts, tables, or animations; or the ways of presenting information might stay the same, while the techniques for making selections and issuing commands might be different.

One major area of software development that can benefit from reusable software is the design and implementation of applications having graphical user interfaces. As a consequence of the widespread availability of bit-mapped displays, the attention of many programmers shifted from emphasis on implementing models of data, transactions, and/or business functions, to creating appealing user interfaces. Requirements for adhering to the many user interface standards have not decreased the importance of seeking innovative ways to present information on the display screen and to support users in accessing and manipulating that information.

Because reusability of user components is a key element in improving the productivity of software developers, an important question to answer is which software components should be targeted for reuse? This article answers the reuse question through a review of the implementation architecture called "Model-View-Controller" (MVC) that is available to application developers using the Objectworks for Smalltalk-80 program development system developed by ParcPlace Systems. The intent of the article is to demonstrate the advantages of this architecture in developing graphical interactive applications.

MVC

The Model-View-Controller architecture for the Objectworks for Smalltalk-80 system was designed specifically to help programmers create graphical user interfaces through the reuse of software components that represent the presentation and interaction aspects of an application. The use of object-oriented software was essential in motivating the design for MVC and was the basis for its implementation.

One way to improve productivity in software development is to reuse user interface designs across different applications. Another entails leveraging special support for multiple ways of viewing and interacting with an information model in accordance with diverse user preferences. For example, some users might want to view their data as tables of numbers, while others might prefer graphs, descriptive text, or animations. These alternatives might be mapped onto a set of software components that can be attached interchangeably to a common information model. The information model is then shared among the projects requiring the different graphical interfaces. The development effort and cost involved in implementing the information model is incurred just once. Many different information models can be presented with similar user interfaces.

Recognizing and exploiting these opportunities for reuse led to the design of two kinds of elements in Objectworks for Smalltalk-80 -- a library of presentation and interaction components, and a set of tools supporting a methodology for linking presentation and interaction components to underlying information models. These kinds of elements provide the basic building blocks for the MVC architecture.

"Model" refers to a representation of the application domain as an information model, while "view" is a specification of how aspects of a model are presented to the user. The "controller" is the specification of how the user can communicate or interact with the application in order to request changes in the view or in the underlying model. Consider, for example, a real-time clock that is to appear on the display screen. The information model is an object representing DateAndTime, which is implemented in terms of the computer hardware's built-in clock. Suppose DateAndTime can report the current time of day and the name of the current day of the current month. On the screen, DateAndTime might appear as a digital watch in which the day and month are printed on one text line and the hours:minutes:seconds appear, constantly updated, on a second text line. This text-oriented presentation is the "view" of the model DateAndTime.

Another view might be of an analog clock, with a second hand moving around, ticking off seconds. Perhaps the user has direct access to resetting the time information. In the textual view, the user could select the text of hours:minutes:seconds with a pointing device such as a mouse and then use a text editor to modify the visible information. This in turn would modify the underlying DateAndTime model, and the change would be reported back to the view that is an analog clock for immediate update. The user's ability to text edit is handled by a "controller."

The design of the MVC roles in Smalltalk-80 came about as a result of a two-stage factoring. First, the design and implementation of the domain-specific aspects (the model) is separated from work on the user interface. Second, the user interface is divided into presentation and interaction aspects.

The ideal scenario for the construction of application software under the MVC paradigm begins by focusing on the design and implementation of the information model components of an application. (Even here, there are substantial opportunities for reuse from previous projects.) Once the underlying model is completed, the developers reach into a library of reusable user interface components and select (and tailor) various graphical presentations and styles of user interaction appropriate for the target users' preferences. In this way, it is possible to create several implementations quickly, varying the presentation and interaction styles according to user needs. In our experience, it is common for the underlying model and presentation style to stay fixed across a broad set of end users, whereas interaction style (such as typing commands versus command keys, or the selection of icons) varies considerably. By separating the notion of presentation from interaction, this disparity can be accommodated.

Reuse Through MVC

The ability to reuse software components depends, of course, on the ability of the software system or language to describe and maintain identifiable modules. The current wisdom in the software industry is that such a module is a packaging of behavior (procedures) and properties that both describe the module and comprise the data required to implement the behavior. The module is referred to as an object; a system that supports the description, implementation, and testing of objects is said to be object oriented. Such a system must also provide tools for factoring and re-factoring modules in terms of abstract and concrete specifications of behaviors to be represented by modules of various scopes and complexities. The software developer implements an application by creating objects that combine, refine (or specialize), and/or establish information dependencies among existing (that is, reusable) and newly synthesized object types. The MVC approach suggests factoring a system into three kinds of objects. Model objects share a generic ability to inform views and controllers of changes in their state. View objects understand how to draw graphical representations of a Model within an identified area of the display screen and how to update those representations according to information solicited from the Model. Controller objects support a variety of default-and developer-defined ways of handling user events, scheduling access to Views supporting user interaction via input devices, and sending editing commands to Views or Models. Each of these three kinds of objects is defined in terms of a hierarchy of object descriptions, as shown in Figure 1.

Figure 1: Hierarchy of Models, Views, and Controllers for Text (each subclass adds a refinement in behavior)

  Model
          Text
          TextCollection
                Terminal

  View
          TextView
          CodeView
                OnlyWhenSelectedCodeView

  Controller
          MouseMenuController
                ScrollController
                        TextEditor
                                CodeController
                                      AlwaysAcceptCodeController
                                      OnlyWhenSelectedCodeController

Reusing the Expertise of Others

The technical history of MVC is tied to the idea that programmers are not typically trained as graphic artists nor as human-factor engineers. But the knowledge of such experts could be captured, in the form of presentation techniques and interaction frameworks, and made available in a library of reusable software components. The factoring of MVC draws attention to particular kinds of components that should be made available for reuse in order to take advantage of the knowledge of these experts.

Two programming techniques are needed to support such reuse: Composition and refinement. Composition entails combining existing elements to create something new. Objectworks for Smalltalk-80 supports two kinds of composition: Composition via delegation, stating that a newly created object has properties that refer to (instances of) existing classes (an action that requires manipulation of these properties is delegated to the appropriate object); and composition via dependency, stating that an instance of one class depends on the change in behavior of (instances of) another class.

Additionally, two kinds of refinement are supported: Refinement via inheritance (subclassing), stating that a newly created object acquires the properties, protocol, and implementation of the protocol, of an existing class; and refinement via parametrization, stating that a new object is an instance of an existing class of objects by filling in details about the properties that distinguish this instance from other instances of the class.

Through composition, you can reuse existing models (that correspond to generic objects), existing views, and controllers that define a look and feel for the user interface and existing tools, for example, a text editor or document outline browser.

Through refinement, you can reuse existing application-building frameworks that encompass sets of views and controllers, as well as parametrisizable models. You can also reuse existing parameterizable, tailorable complete applications.

Through composition and refinement, object-oriented MVC design allows the software analyst/designer to focus on creating components that are immediately reusable and that provide for future incorporation of new views, controllers, and/or models.

A Model for Counting

The Smalltalk-80 code in Listing One (page 106) consists of a simple model for maintaining a numeric counter, associated views, and controllers. This model is called "Counter." Using a variety of viewing objects, the current value of the counter is presented on the display screen in five different ways. Two viewing classes are offered in the example: CounterView and BarView. The same controller is used for all of these views. It specifies that the user selects a menu item, either "Increment" or "Decrement," in order to change the numeric value by an increment or decrement of one. Presentation of a menu that appears when the user presses a mouse button is handled by the controller, an instance of class Counter-Controller.

The five examples are shown in Figure 2, and the initialization messages to classes CounterView and BarView are given in the comment of the code for the classes.

Note that in the Objectworks for Smalltalk-80 system, the library of reusable components includes the classes shown in Table 1, in addition to classes Model, View, and Controller.

Table 1: Classes for reusable components

  Class                     Function
---------------------------------------------------------------------------

  Form                      Represents a rectangular pattern of dots.

  Text                      Represents a string of displayable characters
                            with emphasis and font change.

  Switch                    Represents a selection setting and actions to
                            take on a change in the setting.  A Switch has
                            three attributes: state (either on or off); on
                            action; and off action.  The on and off actions
                            are blocks of code that execute whenever the
                            Switch changes state.

  Button                    A Switch that turns off automatically after
                            being turned on, that is, it acts like a
                            push-button switch.

  SwitchView and            The view presents a switch on the display
  SwitchController          screen, showing a label when the switch is off
                            and showing a special highlight form when the
                            switch is on; the controller knows a message to
                            be sent to the Switch whenever it is selected.

  PopUpMenu                 Represents a list of items presented on the
                            display screen in a rectangular area.  As the
                            user points to an item, pressing a mouse
                            button, the item is highlighted; when the
                            button is released, the last highlighted item
                            is the selection.

  MouseMenuController       Whose behavior is to check for user mouse
                            button events and invoke an action according to
                            which button is pressed.

  StandardSystem/View and   A presentation of a simple window with a title
  StandardSystemController  label above the top left corner, whose behavior
                            is to move, reframe, collapse, and close.

The algorithm for displaying a view is the response to the message displayView as defined in classes CounterView and BarView. Note that in Listing One, double quote marks surround comments that are dispersed throughout the code. Formatting is done to aid readability.

By initializing each view with the messages shown in Figure 1 with the same model of a counter (that is, where aModel: = Counter new), all the views present the same numeric values and each view updates whenever the counter value is changed.

Advantages of the MVC Approach

Three primary advantages derive from adopting the MVC approach and using it to factor graphical interactive applications. The advantages are: Multiple Viewing, Development Productivity, and Quality. Let's look at each briefly.

Multiple Viewing. Factoring the underlying model from its graphical presentation and interaction allows the programmer to couple the model to several alternative interfaces. Objectworks for Smalltalk-80 provides special support for objects to exchange information about changes. Whenever an object changes, it broadcasts a message that it has changed and what aspect (property) has changed. By combining this support for dependency relationships with the factoring of views and controllers from the underlying model, several views of the same model can be interacted with simultaneously on the same display.

Development Productivity. As noted earlier, it is often possible to create new views and controllers as refinements of existing ones, while retaining the underlying information models. It is also often necessary for the programmer only to implement specific kinds of models that fit into existing user interface designs. Therefore, taking advantage of the reusability of existing MVC components and frameworks can reduce significantly the amount of programming necessary for completing an application development project.

The programmer must be aware of the contents of the available libraries of reusable components and have tools for locating and experimenting with those components. Objectworks for Smalltalk-80, for example, provides the system source code browser with MVC components carefully organized into categories. Each of the program development tools provided with the product are created using the MVC approach. The programmer can inspect the implementation of these tools using the ParcPlace debugger and special MVC inspector.

MVC factoring also facilitates the programmer's ability to respond to new technology for special physical needs or interaction media. For example, this factoring makes it a simpler task to utilize voice output instead of printed text on a display screen. Only the presentation aspects of an MVC-style application would change, not the underlying model.

Quality. Reusing existing components allows those components to become more mature and, therefore, more robust as they are tested in new situations. Reusing existing designs supports the acquisition of the expertise that might not otherwise be available. And improved productivity allows for more experimentation and testing.

The Future

MVC was first explored and tested in earlier versions of the Objectworks for Smalltalk-80 system. It was designed to enable reuse of classes representing views and controllers, with models that were otherwise nongraphical. The current implementation was designed with assumptions about the sophistication of the programmer who is able to reuse components from an extensive library. The desire for uniformity of software architecture encouraged the application of the MVC design concepts to text and picture editors, creating considerable experimentation and discussion about the idea of splitting techniques of selection, scrolling, and zooming from these underlying graphical entities. The original MVC was not designed to deal specifically with underlying models that are themselves graphical in nature. We were primarily trying to support visualization of simulations written in Smalltalk-80, itself designed as a language that supports general-simulation descriptions. Over years of use, we have discovered simplifications and new abstractions that make these performance and implementation issues easier to understand. New improvements will appear in subsequent releases of Objectworks for Smalltalk-80.

_INFORMATION MODELS, VIEWS, AND CONTROLLERS_ by Adele Goldberg

[LISTING ONE]




View subclass: #BarView
   instanceVariableNames: 'maximumValue '
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Demo-Counter'!

!BarView methodsFor: 'accessing'!

barFrame
   ^self insetDisplayBox insetBy: (50 @ 10 corner: 10 @ 10)!
labelCount
   ^5!
maximumValue
   ^maximumValue!
maximumValue: anInteger
   maximumValue _ anInteger!
positionFor: value
   ^self barFrame height * value / self maximumValue! !
!BarView methodsFor: 'displaying'!
clearBar
   | height bar corner |

   corner _ self barFrame bottomLeft.
   height _ self positionFor: self model value + 1.
   height _ height min: (self insetDisplayBox height-10).
   bar _ corner - (0 @ height) extent: self barFrame width @ height.
   Display white: bar.!
displayBar
   | height bar corner |
   corner _ self barFrame bottomLeft.
   height _ self positionFor: self model value.
   height _ height min: (self insetDisplayBox height-10).
   bar _ corner - (0 @ height) extent: self barFrame width @ height.
   Display black: bar.
   Display fill: (bar insetBy: 2 @ 2)
      mask: Form darkGray.!
displayView
   self displayYLabels.
   self displayBar!
displayYLabels
   | count label increment |
   count _ self labelCount.
   increment _ self maximumValue / count.
   label _ 0.
   (count+1) timesRepeat:
      [label printString displayAt: (self barFrame bottomLeft -
                                         (35 @ ((self positionFor: label)+8))).
       label _ label + increment]!
update: aParameter
   self clearBar.
   self displayBar.! !
!BarView methodsFor: 'controller access'!

defaultControllerClass
   "Answer the class of a typically useful controller."

   ^CounterController! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

BarView class
   instanceVariableNames: ''!

!BarView class methodsFor: 'instance creation'!

newWithHeight: aNumber
   "Create a new BarView for displaying value between 0 and aNumber"
   ^super new maximumValue: aNumber!

open: aModel
   "Open a view for a new counter."
   "BarView open: Counter new."

   | aBarView topView |

   aBarView _ (BarView newWithHeight: 10) model: aModel.
   aBarView borderWidth: 2.
   aBarView insideColor: Form white.

   topView _ StandardSystemView new label: 'Counter'.
   topView minimumSize: 80@40.
   topView addSubView: aBarView.

   topView controller open!

open: aModel withHeight: anInteger
   "Open a view for a new counter."

   "BarView open: Counter new withHeight: 20."

   | aBarView topView |

   aBarView _ (BarView newWithHeight: anInteger) model: aModel.
   aBarView borderWidth: 2.
   aBarView insideColor: Form white.

   topView _ StandardSystemView new label: 'Counter'.
   topView minimumSize: 80@40.
   topView addSubView: aBarView.

   topView controller open! !

View subclass: #CounterView
   instanceVariableNames: ''
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Demo-Counter'!

!CounterView methodsFor: 'displaying'!
displayView
   "Display the value of the model."

   | box pos |

      "Position the text at the left side of the display area."
   box _ self insetDisplayBox.      "get the view's box"
   pos _ box origin + (4 @ (box extent y / 3)).
                               "put the text 1/3 of the way down to the left"
       "Concatenate the components of the output string and display them."
   ('val: ', self model value asInteger printString, ' ')
                                              asDisplayText displayAt: pos.! !
!CounterView methodsFor: 'updating'!
update: aParameter
   "Simply redisplay everything."
   self display! !

!CounterView methodsFor: 'controller access'!
defaultControllerClass
   "Answer the class of a typically useful controller."

   ^CounterController! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

CounterView class
   instanceVariableNames: ''!

!CounterView class methodsFor: 'instance creation'!
open: aModel
   "Open a view for a new counter."

   "CounterView open: Counter new."

   | aCounterView topView |

   aCounterView _ CounterView new model: aModel.
   aCounterView borderWidth: 2.
   aCounterView insideColor: Form white.

   topView _ StandardSystemView new label: 'Counter'.
   topView minimumSize: 80@40.
   topView addSubView: aCounterView.

   topView controller open!

openWithGraphicalButtons: aModel
        "Open a view for a new counter that has fixed graphical buttons for
                                     incrementing and decrementing the value."
   "CounterView openWithGraphicalButtons: Counter new"

   | aCounterView topView switch aSwitchView |

   aCounterView _ CounterView new model: aModel.
   aCounterView insideColor: Form white.

   topView _ StandardSystemView new label: 'Counter'.
   topView minimumSize: 120 @ 80.
   topView maximumSize: 400 @ 300.
                              "add main view"
   topView addSubView: aCounterView
      in: (0.4 @ 0 extent: 0.6 @ 1)
      borderWidth: (0 @ 2 extent: 2 @ 1).

   switch _ Button newOff.       "add increment button"
      switch onAction: [aCounterView model increment].
      aSwitchView _ SwitchView new model: switch.
      aSwitchView label: ('+' asDisplayText form magnifyBy: 2@2).
      aSwitchView insideColor: Form lightGray.
   topView addSubView: aSwitchView
      in: (0 @ 0 extent: 0.4 @ 0.5)
      borderWidth: (2 @ 2 extent: 0 @ 0).

   switch _ Button newOff.    "add decrement button"
      switch onAction: [aCounterView model decrement].
      aSwitchView _ SwitchView new model: switch.
      aSwitchView label: ('-' asDisplayText form magnifyBy: 2@2).
      aSwitchView insideColor: Form lightGray.
   topView addSubView: aSwitchView
      in: (0 @ 0.5 extent: 0.4 @ 0.5)
      borderWidth: (2 @ 1 extent: 0 @ 2).
   topView controller open!

openWithTextButtons: aModel
   "Open a view for a new counter that has fixed text buttons for
                                     incrementing and decrementing the value."
   "CounterView openWithTextButtons: Counter new"

   | aCounterView topView switch aSwitchView |

   aCounterView _ CounterView new model: aModel.
   aCounterView insideColor: Form white.

   topView _ StandardSystemView new label: 'Counter'.
   topView minimumSize: 160 @ 60.
   topView addSubView: aCounterView
      in: (0 @ 0 extent: 1 @ 0.6)
      borderWidth: 2.
   switch _ Button newOff.    "increment button"
      switch onAction: [aCounterView model increment].
      aSwitchView _ SwitchView new model: switch.
      aSwitchView label: (Text string: 'increment' emphasis:
                                                              2) asDisplayText.
      aSwitchView insideColor: Form white.
   topView addSubView: aSwitchView
      in: (0 @ 0.6 extent: 0.5 @ 0.4)
      borderWidth: (2 @ 0 extent: 0 @ 2).
   switch _ Button newOff.    "decrement button"
      switch onAction: [aCounterView model decrement].
      aSwitchView _ SwitchView new model: switch.
      aSwitchView label: (Text string: 'decrement' emphasis:
                                                              2) asDisplayText.
      aSwitchView insideColor: Form white.
   topView addSubView: aSwitchView
      in: (0.5 @ 0.6 extent: 0.5 @ 0.4)
      borderWidth: (0 @ 0 extent: 2 @ 2).
   topView controller open! !

MouseMenuController subclass: #CounterController
   instanceVariableNames: ''
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Demo-Counter'!

!CounterController methodsFor: 'initialize-release'!
initialize
     "Initialize a pop-up menu of commands for changing the value
                                                                of the model. "
      super initialize.
      self yellowButtonMenu: (PopUpMenu labelList: #((Increment Decrement ) ))
           yellowButtonMessages: #(increment decrement )! !

!CounterController methodsFor: 'control defaults'!
isControlActive
   "Take control when the blue button is not pressed."
   ^super isControlActive & sensor blueButtonPressed not! !

!CounterController methodsFor: 'menu messages'!
decrement
   "Subtract 1 from the value of the counter."
   self model decrement!
increment
   "Add 1 to the value of the counter."
   self model increment! !
Model subclass: #Counter
   instanceVariableNames: 'value '
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Demo-Counter'!

!Counter methodsFor: 'initialize-release'!
initialize
   "Set the initial value to 0."
   self value: 0! !

!Counter methodsFor: 'accessing'!
getValue
   "Return the current value of 'value'"
   ^value!

setValue: aNumber
   "Set the value of 'value' to be aNumber"
   value := aNumber!

value
   "Answer the current value of the receiver."
   ^value!

value: aNumber
   "Set the counter to aNumber."
   value _ aNumber.
     self changed! !

!Counter methodsFor: 'operations'!
decrement
   "Subtract 1 from the value of the counter."
   self value: self value - 1!

increment
   "Add 1 to the value of the counter."
   self value: self value + 1!

printOn: aStream
   aStream nextPutAll: 'a CounterHolder with value ', self value printString! !

!Counter methodsFor: 'printing'! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

Counter class
   instanceVariableNames: ''!

!Counter class methodsFor: 'instance creation'!
new
   "Answer an initialized instance of the receiver."
   ^super new initialize! !









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.