Rohit
Resent-Date: Wed, 7 Aug 1996 12:34:54 -0600
X-Sender: johnson@128.174.252.12
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Date: Wed, 7 Aug 1996 12:34:54 -0600
To: gang-of-4-patterns@sal.cs.uiuc.edu
From: johnson@cs.uiuc.edu (Ralph E. Johnson)
Subject: Re: Data_View_Rules
Cc: strong-java@entmp.org, bikash@wipsys.stph.net, vijay@wipsys.stph.net,
praveend@wipsys.stph.net, droberts@sal.cs.uiuc.edu,
brant@sal.cs.uiuc.edu
Resent-From: gang-of-4-patterns@cs.uiuc.edu
X-Mailing-List: <gang-of-4-patterns@cs.uiuc.edu> archive/latest/306
X-Loop: gang-of-4-patterns@cs.uiuc.edu
Precedence: list
Resent-Sender: gang-of-4-patterns-request@cs.uiuc.edu
You should take a look at papers on constraint systems. I'm not an expert in that topic,
though I have read some of the literature. I have used a constraint system that
was written by Bjorn Freeman-Benson, who was a student of Alan Borning. John
Vlissides (and Richard Helm, too, I think) worked on integrating a constraint system
into a drawing editor. There are papers on these systems, though I don't know the
references. There has been at least one conference on constraint systems,
and a dozen PhD theses. So, there is lots of literature on the subject.
I think of the Observer pattern as the "poor man's" way to handle constraints.
When your system gets complicated, Observer breaks down and you have to use something
more powerful. But constraint systems can be very complicated. I do my best to avoid
them, because I never am able to completely predict what they do. They usually work
well, and can be both efficient and easy to use. But every so often they act in ways
that are hard to explain, and it is hard to predict when that will happen. Even if
you don't want to have a complete constraint system, there is a lot you can learn from
them.
People have already talked about the need to handle cycles and evaluating each rule
only once, so I won't say anything about it. This is what people usually talk about
in papers on constraints. I'll talk about something both simpler and less discussed,
which is how the constraint objects get data from the application objects.
Bikash told me (in a private e-mail)
>This is the design of Data-View-Rule classes for application. There is a one-to-one
>correspondence between a Data instance , a View instance, and a Rule instance.
>Each concrete class in the system should inherit from these three base classes. Each concrete
>data class creates an instance of Rule class. For each attribute of the concrete Data class ,
>there exists a validation function in the corresponding concrete rule class. The concrete data
>class forwards the validation request of an attribute to the rule object specifying the serial
>number of the attribute in the data class. Depending on the attribute's serial number the
>appropriate function is invoked. The deficiency in this design is that , The rule object is
>tightly coupled to the data classes order of attributes. This a major constraint.
There are a couple of ways to interpret this. One is that a Data is a fairly primitive
object like a date or money, and not a complex object like a employee or an invoice.
If a Data is a primitive object, you will have a fairly small number of them and so will
not constantly be creating new Rules and Views. On the other hand, if it is a complex
object then every application will have some new Data, Rule, and View classes.
My reaction is that I don't like having to make a new Rule class for every Data class.
You should be able to make a set of View classes and Rule classes that work for most
applications. You want to be able to extend them because no fixed set of classes will
work for all applications, but you should be able to handle 80% of the applications with
them. This is usually my goal when I am designing a framework.
In my opinion, you will want to have a fairly small set of primitive Data, Rule, and View
classes, and you will want composite classes that let you build up complex ones. The
primitive Data classes are example of Whole Value from the "Checks" pattern language, which
in in the first PLoPD and which you can get by ftp from st.cs.uiuc.edu in
/pub/patterns/plop-papers/checks.ps. You can also see it on Ward Cunningham's server
at http://c2.com/ppr. "Checks" is a pattern language about how to design systems to check
user input, and is obviously relevant to this discussion.
Checks says that a lot of rules should be the responsibility of the Whole Values.
A date object should make sure that "Jan 40, 1995" is not legal. The Employee object
should make sure that the age of an employee is greater than 15 (or whatever your
minimum age is). But there are still rules that are left out, that need to be checked
using a system like the one Bikash described.
The patterns I will discuss are from VisualWorks (the latest version of MVC from ParcPlace)
and Bjorn Freeman-Benson's DeltaBlue constraint system.
The first thing you need to do is to reify the attributes of a Data class. You want to
have constraints on individual attributes, not just on the entire Data object. All the
constraint systems do this one way or another. A simple way to do it is to make something
like the ValueHolder class in VisualWorks, which is an object that holds a single value.
You can read its value, set its value, or depend on its value. (It is a subject in the
Observer pattern). When a ValueHolder gets a new value, it notifies its dependents.
DeltaBlue had the same thing, and called it a ConstraintVariable.
Note that what we are doing is breaking a Subject into pieces, making the Data objects
sort of composite subjects. The observers (i.e. the Views) will be composite, too.
Each observer will need to find its subject. There will need to be a script of some
kind that connects observers to subjects, which probably means you will need to have some
way to refer to the components of a complex subject. In Smalltalk, you can do this by
naming the message you send to the subject to get its component, but this probably won't
work in Java or C++. Instead, you should store the components in a dictionary, or at
least provide a dictionary-like way to get them by performing the componentNamed(aString)
operation on the Data to get its ValueHolders. Another way of saying this is that we
will turn the variables of the Data into objects so we can depend on them, and will
provide a mechanism so that clients can ask the Data for a particular variable-object
by name.
This makes it much easier to build composite views. You can specify a composite view
by specifying its component views and the name of the data component that each view
is using. In other words, you can specify the subject of each observer by specifying
its name in the composite subject.
This will also make your Rules a lot more reusable. For example, a common constraint
is that a variable must be within a certain range. You can make a generic Rule that
takes the name of the variable to check and the bounds of the range. It watches the
ValueHolder and complains if it goes out of bounds.
Earlier I said that a lot of Rules could be handled directly by the Data object.
For primitive Data objects, this is usually builtin when you write the code. But if
you are making composite Data objects, there is often not a special class for the Data,
but just a script that composes it. In this case, you probably want to associate a
Rule with the composite Data, and to specify it in the script that builds the Data.
When a Rule is stored in the Data, it is sort of like a strategy. But it might just
be an observer, and never triggered directly by the Data, but only indirectly when the
ValueHolders change.
But it is also reasonable to store Rules in a View, and then they are usually strategies.
The View depends on the Data, not the Rule, and the View triggers its Rule when the Data
changes. In fact, I don't see why Rules would be anything except data-rules (which will
be part of the domain model) or view-rules (which will be part of the application model).
Once you have made the rest of the system composable, you will want to make Rules
composable, and that is an application of the Interpreter pattern. You will have
primitive Rules such as RangeRule and ComparisonRule (i.e. "x < 20"), and you will
have Expressions (such as a variable, the sum of two expressions, or the difference
between two expressions) that return non-boolean values and can be used in a RangeRule
or ComparisonRule, and composite rules such as AndRule and OrRule. The Interpreter
pattern comes because there will be a value() operation on rules to find out whether
they are being satisfied. A value of "false" means they are not.
This whole process is a typical example of how white-box frameworks evolve into
black-box frameworks. Don Roberts and I am writing some patterns on this, which
you can find at http://st-www.cs.uiuc.edu/users/droberts/evolve.html
Comments are appreciated!
I could give more detail, but this is long enough already and maybe it is clear enough.
If it isn't, tell me which parts to elaborate.
-Ralph Johnson