So far we have hardly discussed data, and even then quite indirectly. Before implementing presenters, therefore, we need establish a better foundation for understanding and dealing with issues related to the nature of data. The whole purpose of presenters, after all, is to facilitate the creation of interfaces through which the user interacts with data.
We take as our starting point the notion of value. A value is an entity that (1) can be compared to another such entity, and (2) can be operated upon to create another value. Two values that can be compared to each other and which have similar operators are of the same type. Comparison of two or more values reveals their equality or inequality, or some kind of ordering. A value that results from an operation on one or more values is derived; otherwise it is said to be original. A type of value that cannot be separated semantically into other, simpler values is atomic or (scalar), and one that is made up of component values is assembled (or generated). A variable is an entity having a value that can be changed. A model is a variable (with additional features besides).
Each model (datum) has one value at any given time. For a composite model, this value is assembled from its sub-units. For a scalar model, which has no sub-units, the value is said to be atomic. This is not to imply that a scalar value cannot be arbitrarily complex, but merely that it cannot be generated from other data elements.
Python is a dynamically-typed language, meaning that the type of an object assigned to a given name is checked only at runtime. Each model, however, will nearly always need to hold values of a specific type. One might consider the datatype of a model to be a kind of constraint, restricting the set of values that a model can accept. It is probably more useful, however, to think of the datatype in terms of representations.
Typically a value can be represented in more than one way, often many. A number, for example, could be represented as an integer or a floating-point number, or as a string. We can say that the value of a model (a variable) may have many possible representations, but the value of a model will have only one actual representation. This is the representation that the model uses to hold its value internally, and which determine the kinds of operations (and comparisons) that can be performed on the value.
GUI applications are properly described as event-driven. Typically the events that drive program logic or navigation are key-board or mouse events intiated by the user. The conventional way of managing such events is through direct one-to-one coupling to event handlers (functions) via macros as described above. There are situations, however, where in and of itself, merely changing the value of a variable should be considered an event to which other parts (often several other parts) of the application may respond. In this case, we need a looser (i.e., scaleable), one-to-many coupling mechanism.
The so-called observable-observer (also model-view) design pattern provides an outline for such a one-to-many coupling mechanism. In this pattern, observable objects maintain a list of observers, each of which has been registered via an AddObserver() method of the observable. Each observer also implements an Update() method, to be called by the observable when it notifies the observer of a "change event". This notification need not always take place immediately upon the observable changing state, but rather when some Notify() method (or its equivalent) is called.
A parameter may be passed from the observable to the observers via the call to the Update() method. If this parameter provides all the data needed by the observer, such a mechanism is called a push notification. On the other hand, if no parameter is provided and the observer is only notified that a change has taken place, the observer is then responsible for requesting the data it needs. This is called a pull notification mechanism. Or in between, the observable may pass certain other information (such as the identity of the object that triggered the change event) to the observer, which it can use to obtain the information it needs . This is called a push-pull mechanism of notification.
Most implementations of the observable-observer pattern provide a method for removing an observer from the list when it no longer needs to be notified of change events. One variation is some mechanism for transparent cleanup, automatically removing an observer when it is deleted from the application. Furthermore there are more sophisticated implementations that provide (1) even looser coupling, (2) more flexible notification mechanisms and (3) centralized services (see Global Signal Dispatching).
Each model can be a node in a tree graph. The model naming and binding mechanism is based on the premise that conceptual objects are richly structured hierarchies, best represented by directed, acyclic, labeled graphs.[1]
None,In Python there are (at least) three ways of implementing this kind of tree graph. The first is using nested dictionaries. The second is through the the class definition syntax, where child nodes become attributes of the parent instance. The third is something in between, in which the parent object keeps a special attribute for a list or dictionary of its children.
Constraints are rules we write into the design of the abstraction layer (models) of our application that help enforce the integrity of the data. We stated above that models are variables: entities having a value that may be changed. Not every conceivable value, however, is valid. Constraints help us make sure that invalid values are prevented and/or detected and fixed before they cause harm.
It should also be pointed out that many classes of data elements are identical in function, and differ only in one or more of the constraints outlined below. In this case we may describe the definition of such classes as specialization by constraint. In the implementation phase we will need to arrive at a notation to facilitate this kind of specialization.
One of the inexorable problems in database management is how to deal with missing information or invalid values. There are essentially two approaches to this issue: (1) default logic and (2) three-value logic.
default logic A naive but often effective way of dealing with missing information is to replace it with a so-called default value. This is based on the assumption that a pre-determined value is more likely to be accurate than none at all, an assumption that may in fact be wrong in many situations. With careful selection of a default value, however, this may be the best approach. An attractive option is to use 'unknown' or some equivalent for the default value, and this often works quite well, depending on the conclusions that need to be drawn (but for short-comings, see three-value logic below). Using the empty string ('') or zero (0.0) for unknown values is notorious for causing problems where either of these may be a valid known value. Two forms of default logic are credulous and skeptical.
Default logic in the context of data management is not the same as, but is akin to default theory in the context of reasoning systems, the latter dealing with default rules rather than default values. It should also be pointed out that default values can be useful entirely apart from the logic of dealing with missing information. Presenting the user with a default value, to be accepted or modified, is often considered to be a user-friendly approach. This is clearly not the same as missing information.
three-value logic In three-value logic, unknown values are assigned a special default (null or None) value, which in the context of logic becomes the "third value" (in addition to True and False). A new set of truth tables is thus required, but the consequences of this scheme are far reaching and not always obvious. We shall allow the default to be None, but caution that care must be taken in interpreting the results of mathematical, logical or relational operations based on such values.
bad value A special case of missing information is an actual value that is known to be wrong or suspect, but which may be better than no information at all (last known address or phone number, for example).
In the context of user interfaces especially, so called pick-lists are commonly employed to make entering information easier and safer. Clicking on an item in a list is generally considered easier than typing out a long word or phrase. An in the case of a short two-letter state abbreviation, for example, picking from a list can prevent errors. A variation on this theme is autocompletion.
In the context of database management, validation of a text entry might include checking to see if it matches an item in the 'choices' list. Except for simple words or phrases, however, this is usually not a good idea because simple typographical mistakes can lead to user frustration. Generally speaking, enhancing the user interface with choice lists is a friendlier approach.
In the context of specialization by constraint, two kinds of inheritance come to mind. (1) A class may inherit the choice list from its superclass, and append new choices to the list. (2) A class may restrict its choices to a subset of the choices defined for its superclass.
minimum / maximum
nested ranges
minSize / maxSize
label
description
units of measure
format
scale
read-only
write-once
...
By now it should be apparent that most contraints mentioned above apply to classes of data elements rather than to specific instances of such elements. It should also be obvious that most of these constraints are, or should be, user-defined. When we get back around to implementing constraints (below) there will need to be some kind of notation that allows the user to specify class-based constraints. We reserve judgement at this point about whether or not some constraints might need to be instance-based as well.
|
XHTML 1.0
© 2003 Donnal C. Walter,
This page updated 2003-11-05. See About this document for information on suggesting changes. |