Constraints


We have a stage, actors, and a script (application context, objects, and a script object plus populated event dispatchers); and the play (application) is ready to open . Except sometimes our actors have to obey rules or are otherwise constrained in their actions. We need to accommodate this need as well.

Objects, like people, can have behaviors that should not be exercised in certain circumstances. All constraints are local and arbitrary (for example, the rules of culture that allow one behavior in one place but not another), and because of this, the modeling and the implementation of constraints are not intrinsic to the definition of objects, only to the context (application) in which those objects perform at any given time.

Classical thinking about object development made this difficult, primarily because rules and constraints were primarily seen, and modeled , in terms of static relationships between or among objects. Because these relationships were supposedly static, they tended to be hard coded in object designs and implementations .

With a few exceptions, object thinking suggests that constraints and rules should be thought of as objects in and of themselves . We have talked in this vein throughout the earlier chapters and will make the idea explicit in the next section, Self-Evaluating Rules.

Constraints on objects can be direct, inhibiting a behavior (actually allowing or prohibiting the execution of a method) or setting limits on the values of objects contained in instance variables (the knowledge that objects work with when doing their jobs). Constraints can be indirect, reflecting relationships among objects instead of object internals.

Examples of direct constraints include

  • Inability to respond to a message because the object is already engaged in performing work in response to a previous message.

  • Limiting the set of objects to which an object will respond when sent a particular message. For example, the airplane would allow no one except a manufacturer object to send the message that sets its ID (serial number).

  • Restricting the values that can be assumed by an object ”telling an integer that it can assume only values between 10 and 100 would be an example.

An indirect constraint is illustrated with the following example: a marriage object must associate two, and only two, person objects. (Of course, this is a local or cultural rule, not universal, and so does not reveal the intrinsic nature of a marriage object.)

Constraints are revealed at different times in the development process. Stories, told to identify objects, are the most common source. Static models (discussed previously) reveal constraints that might have been assumed in the stories without explicit mention. Thinking about implementation reveals many others, especially constraints on values that objects may assume and state-based constraints.

Note  

The primary sources of constraints are the definition of stories by the customer, the discussions of stories by the customer and the developers, and the development of tests to demonstrate that the stories are correct. The artifacts discussed in this section (interaction diagrams and state charts ) can be used during those three activities.

Implementing constraints must be done with program code. That code is generally located in one of three different places: the affected method, a manager/controller object, or a rule object (by encapsulation). The preferred option is using a rule object. Hard coding makes objects brittle and mandates undue maintenance if changes in constraints occur over time. Manager/controller objects violate object thinking principles (and, from a purely pragmatic point of view, result in unnecessary complexity). Creating a class of objects capable of realizing any kind of rule ”production rule, business rule, formula, and so on ”is the object thinking solution to the constraint problem.

Self-Evaluating Rules

A dependent is eligible for family coverage if she or he is under 18 years of age, between the ages of 18 and 24 but a full-time student and receiving a majority of her or his support from the insured, or if she or he is dependent due to disability or illness .

Your interest rate is the average prime rate (as published in the Wall Street Journal ) for the 30 days preceding the issue date of your statement plus 4.5 percent.

An Employee may have 0 or 1 Spouse.

Py = (1/360 * FA) + (1/12 * (.075 * CB))

The preceding are examples of business rules. Businesses are rife with these kinds of statements, formulas, conditionals, and mandated relationships. Despite the ubiquity and importance of business rules, they are seldom accorded the status of first-class objects. Instead, rules are documented and presented as static relationships among classes. (Traditional development methods deal with rules in the same way ”for example, using entity relationships in a data model.) These static relationships are then supposed to be established and enforced by appropriate code in object methods or by establishing special-purpose objects, such as a database management object, that specialize in the maintenance of relationships.

Object thinking mandates that everything is an object. A rule [6] should therefore be an object and, like any other object, have the ability to perform a specific set of services: modify itself and evaluate itself. By modify, we mean the rule must be flexible: adding, deleting, or extending clauses. By evaluation, we mean applying computational rules to itself so as to return an object encapsulating a meaningful result ”in the simplest case, returning a Boolean object that encapsulates whether the rule was satisfied.

Applying object thinking to the problem of creating a rule object leads to observations about structure and responsibilities.

Structural Abstraction of a Self-Evaluating Rule

A useful object thinking heuristic for decomposition is to take an occurrence of such an object as it appears in the real world and separate its physical parts . We can do this with a simple example of a rule expressed as a formula:

X = 4q + (p*r)

Analysis of this equation (rule) yields five distinct types of physical components :

  • X , q , p , and r represent things that are currently unknown but knowable. Following convention, we will call these items variables .

  • 4 represents a constant value.

  • + and * are behavioral operators .

  • ( and ) in combination represent an aggregation and precedence operator .

  • = is an operator of symmetry , indicating that the thing on one side of the equation is in some sense symmetrical with, equal to, or to be assigned to the thing on the other side.

The order in which these components appear is important, as is the association of elements with one another (for example, determination of the receiver/operator/argument relationship). Our initial, structural, abstraction of a rule then follows :

A rule is an ordered collection of variables, constants, and operators.

Each of these components can be analyzed to discover its behavioral characteristics. Constants are perhaps the simplest, most often being instances of known classes such as integer, float, real, character, string, and so forth, although nothing prevents them from being any known object with the ability to provide a fixed or constant value.

Variables are more interesting. At first glance, we seem to have two different types: those, like X , that equate (are assigned) to the resolution of the entire rule; and those, like p , q , and r , that represent a discrete value that can be used to resolve the rule.

If we think of variables as a place where a value (an object) could be but currently is not, we can posit a structural abstraction for a variable. In the case of variables like X , that abstraction would consist of a targetObject and a setterMessage . For the q , p , and r variables, the abstraction is a sourceObject and a getterMessage . Behaviorally, a variable is responsible for obtaining its value, for instantiating itself. It does so by sending the getterMessage to the indicated sourceObject .

In the realm of objects, we are accustomed to treating operators as messages. In our simple example, we see three nuances of operator: first, those representing standard messages sent to objects to invoke behavior (+ and * being examples); second, those that instruct the compiler/interpreter/virtual machine to establish precedence; and third, assignment operators. The hardest part of implementing self-evaluating rules was dealing with the relationship between operators and the objects related to each. The solution turned out to be creating a term class that had receiver, operator, and ( optionally ) arguments as instance variables. This turned our rule into an ordered collection of terms, but the conceptual essence of self-evaluating rules was preserved.

Our attention can now turn to behavior and the dynamics of the rule and term structures we have defined.

Behavioral Abstraction

Side 1 (responsibility list) of an object cube for a self-evaluating rule would have the following entries: modify self and evaluate self. The second responsibility requires collaboration with the ruleElement objects, namely the constants, variables, and operators.

Side 2 (description and stereotype) might show a stereotype of ordered collection, reflecting the modify self responsibility coupled with the definition of a rule: an ordered collection of variables, constants, and operators.

Side 4 (knowledge required) would indicate a need to know about the ruleElement objects that need to be added, deleted, and so forth but, surprisingly, little else.

Side 5 (message protocol) would define messages for the following: add an element at a location, delete a designated element, evaluate, instantiate, and resolve.

Side 3 (contracts) would show that evaluate, add, and delete messages would be public, while instantiate and resolve would be private.

All basic behavior is now defined. A rule is asked to evaluate itself. It, in turn, sends itself the instantiate message, causing itself to iterate across its elements and ask each variable object to instantiate itself (turn itself into an actual value by sending its message to its receiver). Once all variables are instantiated , the rule then sends itself the message to resolve ”apply the operators appropriately and return the result (with assignment taking place as needed).

Rules are recursive. Any element of a rule can be replaced with a rule. That s more than enough flexibility and power to get yourself into trouble unless you use other object thinking principles, such as simplicity and local action, to eliminate the need for truly complex rules.

Every object can be given the responsibility to validate itself simply by giving it a collection of rules to use as collaborators in that process. Those rules can be modified at run time, just like any other object, merely by sending appropriate modification messages. Variables can be context sensitive in their instantiation.

This type of self-evaluating rule was presumed in examples in previous chapters. The form example assumed that every entry field would have a collection of self-evaluating rules that it would use to validate its contents. Figure  9-20 provides three examples, the first two showing simple entryField validation rules and the third showing a more complex rule for form validation.

click to expand
Figure 9-20: Sample rules with identification of element types.

Creating rules as first-class objects is essential to eliminating the kind of control and management that s endemic to classical software, wherein data is assumed to be passive and special-purpose objects (such as database management systems) assume responsibility for validating all of the elements of data they contain ”with inordinate degrees of complexity as a byproduct.

Implementation

Having thought about all aspects of your objects, your stage, and your scripts (the ones necessary to complete the story you are working on), you are ready to write code ”almost. If you are an XP developer, you are ready to write tests. No matter what kind of developer you are, you need to think about two more details: what your unit tests and code might look like and some additional information that must be provided to those objects whose primary role is the maintenance of bits of knowledge ( data objects).

Methods

For every message you listed on side 5 (message protocol) of the object cube, you have to write a method and a test of that method (or tests). The first tests are obvious: When the message is sent to the object, is the expected object returned? Is it an instance of the right class? Is it in the correct state? By state, I mean does it have a reasonable value; or, if it is a composite object, do all of its instance variables have reasonable values?

The next set of tests concerns an object s ability to recover from errors that it might encounter between the time it s asked to do something and the time it returns an object in response to that request. These tests are trickier because they usually involve a group of objects and a script. A very simple case would be an error that occurs when an entryField object cannot validate itself with the entered object. It must then invoke a dialog box object, ask the dialog to display an appropriate error message and accept an alternative value, and then reperform the validation tests.

I have talked a lot about scripts and noted the possibility of script objects actualizing the sequence of communications among a group of objects. In practice, a lot of the communications talked about in terms of scripts will be implemented in methods. Thinking about scripts helps you design your next set of tests. Does the precondition (trigger) implied in the script always cause the appropriate message to be sent to the appropriate object? Does that object implement the next bit of the script? Does that implementation satisfy the conditions necessary to continue with the larger script?

The filling in of a form is an example of scripts being implemented in methods rather than in an independent script object.

  • The form will receive the instantiate message. The form s instantiate message will be two lines (in some languages) of code: Self, iterate across all entry fields and tell them to instantiate themselves ; and Self, send yourself your validate message.

  • Each entry field will receive the instantiate message. That method will be about three lines: Self, ask your source of input (an object in one of your instance variables) to display itself and return a value to you ; Self, ask your validation rules to evaluate using the entered value ; and Self, let the form (whoever sent you the instantiate message) know that you have successfully completed your job.

  • Each validation rule will receive the evaluate message. The evaluate method will have two lines: Self, instantiate and Self, resolve.

  • Next the validation rule receives the instantiate message. Instantiate consists of one line: Self, tell all your elements that are variables to instantiate themselves.

  • Then the validation rule receives the resolve message. Resolve consists of one line: Send the message value to each of your terms in a recursive fashion.

Writing tests for each of these methods (script fragments ) is relatively straightforward ”until you start to deal with exception and error handling. But even this complication can be ameliorated by creating a new script that might be implemented in several methods in other objects.

Your tests provide any additional specification you might need regarding the form of your methods. Your selected (or mandated) programming language provides you with the syntactic rules that must be adhered to in order to implement the semantics implied by your tests. Unfortunately, most programming languages were not designed specifically to make it easy to express the semantics that object thinking yields. You almost always will have to perform some translation.

Knowledge Maintenance Objects

A lot (perhaps most) of the objects in your application will have the primary charge of maintaining a bit of information. It s convenient to call these data objects ”as long as you remember that they are not passive bits of data; they are real objects!

In addition to the fact that it exists, each data object needs to know a lot of additional information, information that traditionally was specified in a data dictionary or a CRUD matrix.

Note  

A CRUD matrix gets it name from the actions create , read , update , and destroy . It is a simple two-dimensional table that associates users and data items. Each data item in an application appears at the head of a row of the matrix, and each user ( user role) appears as a column header in the matrix. Each intersecting cell then contains one or more of the characters C , R , U , or D , reflecting that users have privileges ( accesses ) to the data item.

Items of importance from a data dictionary include

  • The name of each data item.

  • The composition of each data item ”for example, Name is- composed -of an optional Honorific, and a First Name, and an optional Middle Initial, and a Last Name, and an optional Generational. (Of course, the structure of a name will vary dramatically across cultures.)

  • The allowed size or upper and lower size limits; for example, a string used for a First Name might have to contain at least 1 character but no more than 35.

  • Any initial or default value.

  • A range of values ”what data modelers call a domain; for instance, a Zip Code data item would have a domain of all the values listed in the United States Postal Service Zip Code Directory.

It will be essential to obtain this information about all your data objects. But what do you do with it when obtained? Where is it implemented?

Earlier I suggested that certain classes called primitives are most likely to be used to maintain knowledge ”for example, characters, numbers , strings, dates, times, and money. The obvious place to store CRUD and data dictionary information is in the objects that use that information. However, it is unlikely that you will actually want to modify all of the classes that can be used as primitives. Instead, you might want to create a DataItem class.

A DataItem class is a way to reify passive data into a first-class object. An instance of DataItem would have the following structure (Figure 9-21 shows the object cube representation of this structure):

Label     A string that names the data item.

Class     The name of the class of which this data item is an instance.

Value     The actual value of the data item. This would be an instance of one of the primitive classes.

Composition     An optional collection of DataItem objects that actualizes the data dictionary definition.

Validation rules     A collection of self-evaluating rules that would enforce domain constraints.

Access rule     A rule that enforces the line of the CRUD matrix pertinent to this particular data item.

click to expand
Figure 9-21: Object cube representation of the DataItem class.

If you elect the DataItem class option, you will then return to your object cube, side 4, and replace a lot of the primitives listed there as DataItem objects. Take care to replace only those primitives that are used more or less exclusively as knowledge holders. If you are using a string or number, for instance, for behavioral capabilities and not just to hold a value, you will not want to replace them with DataItem objects.

[6] Many years ago, I formulated the basic ideas of self-evaluating rules and lectured my classes on the concept. I didn t actually have a rule object implemented (leaving that as an exercise for the student). Without implementation, self-evaluating rules were a nice idea, but Kevin Johnson, one of my graduate students, who later became a colleague and great friend, would occasionally complain that rules could not be implemented as I was describing them (as described in this chapter). So one day we sat down and implemented them in Smalltalk, with him doing almost all of the programming. The final concept of self-evaluating rules is the result of Kevin and me discussing them over many months and finally implementing them in at least one object language. He and I coauthored an unpublished paper that is the foundation for most of what is said in this section. I want to thank Kevin and acknowledge his invaluable assistance in finalizing the ideas in this section.




Microsoft Object Thinking
Object Thinking (DV-Microsoft Professional)
ISBN: 0735619654
EAN: 2147483647
Year: 2004
Pages: 88
Authors: David West

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net