Object Internals


Discovery has provided a decomposition of a domain into objects, an assignment of responsibilities to those objects, and a description of how objects might communicate and cooperate with one another to complete tasks beyond the capabilities of individual objects. This is usually not sufficient information to actually build computer software simulations of those objects and of those interactions.

Here are some examples of why this is so:

  • Sides 1 and 2 of the object cube (and the classical CRC card) capture only behavioral expectations of your objects ”they tell you nothing about the internal construction of those objects and nothing about the means used by those objects to fulfill their responsibilities.

  • Assignment of responsibilities to an object tells you nothing about how to invoke that responsibility and nothing about what form the response will take.

  • Identifying a need for collaboration tells you nothing about how the client object uses its collaborator, nor anything about how it knows of the collaborator s existence and location, nor anything about the form of communication with that collaborator.

There is a clear need to think about how your objects will be simulated, to make decisions about construction (design). Those decisions must be guided by the principles of object thinking and based on the intrinsic needs of the object. An intrinsic need is defined as the means for fulfilling an assigned responsibility . It s necessary to discern additional information about our objects and their needs. It s inevitable that the process of determining this information will involve making decisions about implementation, normally an activity associated with design.

There are innumerable ways to implement even the simplest object or object responsibility. Take, for example, obtaining the string that uniquely identifies an object. Different languages provide different implementations ( object.id , the dot notation of Java and Visual Basic versus the id getter message convention of Smalltalk); different groups set different naming conventions, which necessitate different syntax; and even the nature of the id itself might vary, in some cases being an object, in others a type.

While necessary, design decisions can impose unwanted limitations on the ability to use an object in different contexts. This is reflected in the immense difficulty involved in creating libraries of reusable components and the need for extensive middleware anytime objects created in one context need to be used elsewhere.

Keeping the object s domain-assigned responsibilities in the forefront of your thinking while making design decisions ameliorates design-based limitations. For example, if you are designing an object for use in a database environment, you might be tempted to implement the object so that its internal structure mirrors the table structure of the database. This will limit the use of such an object to that specific implementation environment ”unless you employ middleware or other kinds of interpreters and translators.

If, on the other hand, you continue to let your design be guided by the domain and the behaviors allocated to an object by that domain, you can avoid making decisions that will inhibit the natural composability of that object as it exists in the domain. The following discussion attempts to illustrate the importance of domain-driven design while providing some guidelines for how to accomplish it.

Knowledge Required

If we think of an object as if it were a human being (anthropomorphism) and we give that object a task, it s appropriate to think about what that object may need to know to complete its assignment. We can use our understanding of human beings and what they need to know to perform tasks as a metaphorical guide for our thinking about object knowledge.

For example, suppose we ask Sara to ride her bike to the store and bring home some milk. She will need to know

  • How to ride a bicycle.

  • Where the store is located.

  • A route between home and the store.

  • Perhaps, if more than one store is nearby, which store we want her to go to.

  • The actual quantity of milk required ”disambiguate the word some .

  • If timing is an issue, when we want her to perform this service.

Actually, the list of things required is potentially very large. A lot of the items on our list are assumed to already exist ”for example, how to ride a bicycle. We have criteria (usually tacit) that limit our listing of what Sara needs to know to a few details. We have similar criteria for considering what an object needs to know to fulfill its responsibilities.

If we ask Sara to ride her bike, we probably already know she has claimed that ability and we trust her in making that claim. If an object says it can identify itself, we assume (trust) that the object has a method ”a block of computer code ”that actually performs the identification service. We do not need to explicitly list such abilities as required knowledge. (We will explicitly name the methods themselves when we specify how an object s responsibilities are to be invoked, that is, when we specify a message protocol.)

We will usually assume that Sara knows which store to go to and the route to take unless she has more than one option and it matters to us which option she selects.

We will also assume that now means now and that the service is to be performed immediately upon receipt of the request ”again, unless we want the  option of requesting the service at a specified time or in a particular set of circumstances.

Most of the time, we are interested in recording the information ”knowledge ”required by the object in order to fulfill its advertised services. Information is perilously close to being what we typically think of as data. It s therefore very easy to fall into the computer thinking trap of assuming that knowledge required equals object data structure or object attributes. Object thinking will help us avoid this trap.

The responsibilities recorded on side 1 of the object cube drive our thinking about the knowledge required. Look at each responsibility, and ask what the object will need to know to fulfill this task. List your answer ”as a descriptive noun or noun phrase ”on side 4 of the object cube. Figure 8-1 shows sides 1 and 4 of the airplane object cube introduced in the Another Example sidebar in Chapter 7, Discovery.

click to expand
Figure 8-1: Sides 1 and 4 of the Airplane object cube, showing the relationship between responsibilities and knowledge required.

How did object thinking lead to the results recorded on side 4 of the object cube for the airplane?

  • The airplane is responsible for identifying itself. It therefore needs to know its ID. We record the noun (actually an abbreviation) id on side 4. (See the Object Cube Idiom sidebar coming up for additional explanation of why things are recorded the way they are in the examples.)

  • It needs to report its current location ”hence currentLocation .

  • It needs to move to a new location on request ”therefore, newLocation .

  • Because side 1 of the object cube indicates that serving a request for current location requires collaboration with the airplane s instrument cluster, it needs to know of the instrument cluster itself ”so we record instrumentCluster as a piece of knowledge required.

In most cases, the list of knowledge required will be fairly short and reasonably obvious. In some cases, a single responsibility might yield more than one piece of required knowledge.

Completing a list of items to be known is but the first step in thinking about the object s knowledge requirements. We have two other decisions to make about each piece of knowledge recorded in our list: how it is obtained and what form it takes (what class will be used to encapsulate that knowledge).

start sidebar
Object Cube Idiom

A number of factors play a role in deciding the actual form ”the actual words and symbols ”used to record information on an object cube. Paramount among these is the need to be explicit and avoid ambiguity. For example, the expectations of an object should be obvious from the phrases selected to record those responsibilities; the name of a piece of knowledge should unambiguously describe the semantic understanding of that knowledge; the names given to classes and methods should reflect the essence of those classes and methods. (In this regard, we are very Confucian in our insistence that only if things are given the proper names will all be right under Heaven. )

Countering the need for explicitness is the need for brevity. Eventually most of the names will be used in writing program source code. No coder really likes to type long descriptive names.

Another factor is the syntax of the programming language that the development team is most familiar and comfortable with. Smalltalkers will be quite comfortable recording id: aString as a method name or using Integer as a class name, but C++ programmers are more likely to use id(string) and int in similar circumstances.

This author, like everyone else, is a victim of his past: the idiom I am most comfortable with derives from the use of the Smalltalk programming language and its associated style and idiom. Although I will try to be as non-language-specific as possible in my illustrations, be forewarned that some idioms and conventions will inevitably creep in.

Another example of idiom is naming conventions such as the one that suggests that the method names for retrieving the object in a named variable and for placing an object in that variable are the same as the variable name itself ”with the addition of an argument in the case of the put method. For example, the airplane has a piece of knowledge named id . A  method for retrieving the object encapsulating that ID (usually a string) would be simply id . The method name for replacing the id string with another string would be id(aString) .

The proper idiom for use on object cubes, in code, and in any other phase of modeling or development should reflect the community doing the development. It s the responsibility of the developers in your organization or your domain to determine appropriate idiom and to train new members of your development community in the use of that idiom. XP practices of coding standards, pair programming, and collective ownership of code support the creation and dissemination of appropriate idiom.

end sidebar
 

An object has any of four different ways to gain access to the knowledge it requires:

  • It can store that knowledge in an instance variable.

  • It can ask (in the specification of a message signature) for that knowledge to be provided along with the request for service (the message).

  • It can obtain the knowledge from a third party ”another object.

  • It can manufacture the knowledge at the point when it is required.

Upon deciding which of these options is most appropriate, we will record that decision by noting an appropriate symbol next to each piece of knowledge. The symbols used are arbitrary, but in the examples in this book we will use V for variable, A for argument, C for collaboration, and M for method. Here are several of heuristics for deciding which option to use:

  • Objects are lazy. Every time you decide to store a piece of required information in a variable, the object must assume responsibility for maintaining that variable; it must add the capability to retrieve and to  update the contents of that variable upon request. So whenever possible and appropriate, use argument ( A ) and method ( M ) instead of variable.

  • Collaboration is a form of dependence. Deciding to use a collaborator to obtain required knowledge makes you dependent on that collaborator. Objects strive for independence, so collaboration should also be minimized to the greatest extent possible.

  • Remember the definition of collaboration ”requiring the service of an object not found inside your own encapsulation barrier , which will eventually require direct or indirect coupling with that collaborator object. (With direct coupling, you know the actual name or ID of the object used as a collaborator, whereas with indirect coupling, you know where to find the object ”in a global variable, perhaps.) Coupling of this sort is just as undesirable in object thinking as in any other development method or approach.

  • In addition to knowing who your collaborator is (for example, the instrumentCluster piece of knowledge recorded on side 4 of the Airplane object cube example), you will also need to know the message that will be sent to the collaborator in order to invoke its help. That message might require arguments and, if so, those will have to be added to your knowledge required list. Using collaboration therefore increases the intrinsic complexity of the object using collaboration as a means of access as well as establishing an undesirable coupling between objects.

  • You might want to add the symbol G as an option for recording your decision to access a piece of knowledge. G would stand for collaboration with a global variable. Starting down this road, however, might lead to making a distinction between a global in an application ( G a ); a system global ”the system clock ( G s ), for example; or some sort of intermediate, such as the pool dictionaries in Smalltalk ( G p ). If making such distinctions helps you and your group , there s no harm in using them. Take care that your object cube does not reflect a particular implementation context to the point that the general utility of the object is lost.

A final decision about knowledge required is that of determining the kind of object that will embody (encapsulate) the information. A class name will be recorded for each piece of information listed on side 4 of the object cube. (Using a class name rather than a program language type is preferred for this purpose because some information is complex ”that is, not a simple primitive.) There should be one encapsulating object per knowledge item.

In some cases, you will discover an entirely new kind of object when making this decision ”the location object, for example, used to encapsulate the various components that make up an airplane s location: altitude, latitude, longitude, and vector.

Discovery of the location object is a direct result of applying object thinking to the question of knowledge required. As you think about what a location really is, you discover the various component values that make up a location. You apply the lazy object principle and find out that both the airplane and the instrument cluster find that keeping track of the location components is too taxing and needs to be delegated to someone else. The other candidate is an instrument, but no instrument should know or try to keep track of any value except the one specifically generated by that instrument. Since none of your known objects is a suitable home, you will likely decide to create a new object with responsibilities for recording and maintaining the values that make up a location.

Figure 8-2 shows sides 1 and 4 for the objects in the air traffic control (ATC) example from the Another Example sidebar in Chapter 7. Except for the location object just discussed, the selection of an encapsulating object is pretty much a matter of common sense coupled with knowledge of the existing or planned class library for your domain.

click to expand
Figure 8-2: Sides 1 and 4 of the objects (classes) introduced in the ATC example from the preceding chapter.

Figure 8-3 shows side 1 of the objects in the mortgage trust application discussed in Chapter 7, and Figure 8-4 shows the knowledge required for those objects.

click to expand
Figure 8-3: Object classes from the mortgage trust application introduced in the preceding chapter.
click to expand
Figure 8-4: Knowledge required for objects and responsibilities identified for the mortgage trust application introduced in the preceding chapter.

Message Protocol

Side 1 of the object cube tells us what an object can do but reveals nothing about how to ask for the advertised services. We know from object thinking in  general that services are invoked by the sending of a message to the object providing the service, but what form must the message take? This is not a trivial question because the form of the message is arbitrary, but it must be exact, or the receiving object will ignore it. (Actually, it will cause an error if the message is wrong ”a variation of I haven t the foggiest notion what you are asking me  to do. )

We also have no clue about the way the object will respond to any request sent its way. Will it provide us something in return? In many cases, we hope so. If it does, what will be the nature of the returned item?

To answer these questions, we use side 5 of the object cube to record a message protocol ”a list of messages and their associated responses. As with knowledge required, we refer to side 1 to elicit the necessary list of messages. We also apply whichever idiom and convention for message syntax that s employed in our development environment. As individual messages are recorded, care must be taken to maintain consistency with decisions made elsewhere on the object cube ”notably the names and encapsulating objects noted on side 4 (knowledge required).

Figure 8-5 shows side 1 (responsibilities) and side 5 (draft message protocol) for the objects in the ATC example. Figure 8-6 and Figure 8-7 show, respectively, side 1 and side 5 for the objects in the mortgage trust example.

click to expand
Figure 8-5: Sides 1 and 5 of the objects in the ATC example introduced in the preceding chapter.
click to expand
Figure 8-6: Responsibilities for the objects in the mortgage trust application (object cube side 1).
click to expand
Figure 8-7: Message protocol for the objects in the mortgage trust application (object cube side 5).

Completing side 5 is usually straightforward. The following heuristics help with any nuances involved:

  • A full message signature includes three elements: the message selector (the actual message name), arguments (if any; arguments are optional), and the nature of the object returned to the sender of the message. For example, includes (anObjectSpecification) aCollection . Parsing this message signature results in identification of includes as the message selector, (anObjectSpecification) as the argument, and aCollection as the object returned. Because we are recording the messages received by the object whose cube we are completing, we omit the name of that object when drafting the message protocol; only the last three elements of the message signature are recorded on side 5.

  • Messages must be descriptive of the nature of the service (behavior) being invoked. It should be possible for a naive user of your object to immediately discern what is likely to happen if the indicated message is sent to the object. This includes specification of arguments ”it should be obvious what kinds of objects are being passed as part of the message.

  • Messages must be relatively terse (programmers will get tired of typing long messages), a requirement that is at odds with and less important than the descriptiveness requirement. Programs are read for understanding more frequently than they are typed.

  • Messages should reflect the syntax of your chosen implementation language and any standards or conventions, such as naming, adopted by your development group.

  • If you have decided, on side 4, to store an object encapsulating some bit of information in an object instance variable, you must add two messages to your protocol: one to obtain the object in the variable (a getter message) and one to replace it (a setter message). Either or both of these messages can be categorized as private (see the next section about message contracts), or you can decide to make the variable immutable. It is also possible (relevant if either the getter or setter is considered private) that your language allows access to a variable by reference instead of via an explicit message. (Languages that allow access by reference from outside the object are suspect in terms of object thinking principles and philosophy.) Nevertheless, it is a good idea to record both getter and setter on side 5, even if you elect one of the other implementation options. Two common conventions for getter/setter message style are to use the variable name itself as the getter and the variable name plus argument as the setter; and to append the word get or set to the variable name. (Example for the id variable: id and id: aString ” or getId and setId .) The argument, if any, will reflect whatever decision we made on side 4 as to the nature of the encapsulating object for that variable.

  • Some messages are imperative commands: Don t bother giving me anything back; just do this! In those cases, no object is returned. A Smalltalk convention is to note self as the object returned. For those more familiar with C++ and Java, it is perfectly appropriate to put the   term void in place of self . The meaning is equivalent, although there are still technical differences as to what, if anything, is actually returned.

Message Contracts

Side 3 of the object cube has not been forgotten ”it s only now that it will make some sense to talk about what s recorded on that face of the object cube. That side 3 is not swapped with side 5 is a historical artifact: the idea of contracts precedes the idea of object cubes. A contract is a concept introduced by Rebecca Wirfs-Brock and her coauthors as an extension of the information recorded on the original CRC cards invented by Beck and Cunningham. The idea was to aggregate responsibilities ”later, messages ”into groups to reflect the users of those methods. This particular use of contracts did not gain wide acceptance, and the idea of contracts became rather obscure. Even so, the programming concept of interfaces and templates has much in common with the idea of contracts.

A concept from programming ”message scope or visibility ”provided renewed use for contracts. In a programming language such as Java, methods (and their invoking messages) could be designated public ( anyone can send that message and invoke that service), private (only the object itself can send the message to itself), and protected (only a designated group of user objects can send the message). Other languages, most notably Smalltalk, did not make explicit provision for such method/message declarations. Using contracts to at least specify the intent of the object developer as to the proper use of messages was a natural extension of the notion of contracts. If the implementation language supported message scoping, side 3 provided a specification to the programmer. If not, side 3 documented the intended use of the categorized messages ”and no good object programmer would misuse private or protected messages. (Smile.)

As class libraries grew in size and the messages associated with individual classes grew in number (something that should be minimized if object thinking guides the design of those classes), it proved useful to create subcategories of  classes and of methods to simplify the search for an appropriate class or method in the library. A typical browser in an integrated development environment (IDE) might show the following four lists: categories of classes, class list,  categories of methods, and method list. A user would select the desired class category and see only those classes in that category displayed in the second list. Then the user would select a method category and see only those methods included in that category displayed in the last list. Typical method categories include accessing, displaying, updating, and calculating. Such categories can be captured on the object cube as contracts on side 3.

The layout of side 3 (shown in Figure 8-8) is simple: a contract name followed by an indented list of the messages intended to be included in that contract. A message can appear in more than one contract unless the contracts reflect programming specification of public, protected, and private.

click to expand
Figure 8-8: Contracts for all classes in both the ATC and mortgage trust application examples (object cube side 3).

State Change Notification

Objects encapsulate state. When this claim is made, it s usually a reference to changes in the objects occupying an instance variable. This idea reflects the common definition of state ”a change in value of any aspect or characteristic of a thing ”colored by the way state is used in data-driven object design.

If an object is properly designed, it is typically so simple as to have very little interesting state. Some examples of state might include the following:

  • An object in an instance variable has been replaced with another object; the object (or value of that object) matters far less than the fact that the change occurred, so we would characterize the new state as changed .

  • An object might be in the process of responding to a message and therefore unavailable to receive a new message. State = busy .

  • An object might be defective in some sense, out of calibration or completely inoperative, yielding the states faulty and dead . (In the latter case, we are not looking for dead so much as I m dying, gasp, gasp the butler did it written in blood with the object s last exhalation. Or, less colorfully stated, failing .)

  • An object might be uninitialized : none of its instance variables contain objects other than Nil .

  • An I/O device might have a ready state.

  • A state based on the value of an instance variable: the pressure variable of a steamEngine object and the danger state that would occur if the value of pressure exceeded some limit.

  • From the world of persistence (databases), a variation on the changed state can be surmised: dirty , which means a change that has yet to be reflected in the persistent persona of the object. There is an inconsistency in value between the cell of a database table and the object stored in an instance variable.

Although it is possible for an object to have numerous states, only a few of those states are likely to be of any interest to anyone outside the object itself. In those cases in which other objects might be interested in a state change, it is appropriate to list and describe those states on side 6 of the object cube. The syntax for side 6 is very simple ”a descriptive name of the state and a short description of that state. Figure 8-9 shows side 6 for all objects in the ATC and mortgage trust examples.

click to expand
Figure 8-9: Events for objects in the ATC and mortgage trust examples (object cube side 6). Note that most objects have no events. In some cases this is because they are inherited (MortgageApplication from Form), but usually it just reflects that most objects are not willing to share much state information, except for the almost universal "changed" state inherited from class Object).

Here are some important caveats concerning object state and side 6 of the object cube:

  • Side 6 records only those states that the object is willing to make visible to other objects. In this sense, side 6 is akin to side 1 in that it publishes part of the object s interface: the part of the object visible to others. Some state changes might be kept private to better reflect the domain being simulated by the object. For example, a selfCalibratingInstrument might have a state, out of calibration , that it does not make visible to the outside world. Instead, it detects that state itself, takes steps to correct that state, and only if it fails in such attempts does it generate a public state, failed . Failed would appear on side 6 of the object cube, but out of calibration would not.

  • State changes are visible only to the object experiencing the change. Many objects will choose not to allow anyone to be aware of their changes, and even if an object does make a change public by listing that state on side 6, it does not imply that you ”or any other object ”can see that change, only that you ”and all other objects ”can request to be notified when the object detects the change in  itself.

  • Side 6 of the object cube does not capture and is not intended to  capture state- related constraints on an object s behavior. That kind of information is captured in a static diagram ”specifically a state chart ”and will be discussed in Chapter 9, All the World s a  Stage.

  • Advertising a willingness to notify others of state changes implies that the object has a mechanism for keeping track of who is to be notified, how, and for what. At first this implied requirement might seem to violate object-thinking precepts, but it s quite possible to satisfy such a requirement in a manner consistent with object thinking. The mechanism is an eventDispatcher object. Every object capable (and willing) of notifying others of its state changes must contain an eventDispatcher to effect that notification.

An eventDispatcher object can be visualized as a simple two-part table, as shown in Figure 8-10. The first column of the table contains events, one per row. The second column of the table contains a collection of eventRegistration objects. An eventRegistration , in its simplest form, is a two-part object consisting of a receiver and a message .


Figure 8-10: An event dispatcher table and an event registration tuple.

Creation of an eventDispatcher object increases flexibility by centralizing but not controlling the awareness of which events exist and who is to be notified when they occur. If an object decides to publish a new event, a simple message to the eventDispatcher requesting the addition of a new row to the table is sufficient to effect that change. An object s eventDispatcher can be queried as to which events are available, and it responds with a collection (a list) of the contents of the  first column. Such queries are almost always made by the programmer as she decides what objects and scripts need to be created to realize a story. Only if  you  were building a complex-adaptive system (artificial life, for example) would you expect other objects to query a dispatcher about its event list at run time.

If objectA wants to know about a state change in objectB (one that objectB has advertised as public), it sends a registration of its own construction to objectB s eventDispatcher . The objectA object decides what message it wants sent to effect the event notification, which means that objectA can change its mind ”use different messages in different contexts ”simply by asking that its  earlier registration be replaced with a new one containing a new message. This capability provides significant run-time flexibility, allowing changes without necessitating any changes in code and subsequent recompilation of objectB .

One other possibility to be noted: suppose we need to notify objects in  a particular order ”for example, some objects need to be notified immediately and the needs of others are less urgent. We can change (probably subclass) eventRegistration to be a triple ” receiverId , message, priority. We  can then allow the collection that makes up the second column of the eventDispatcher to be a sorted collection. As eventRegistration objects are added, they are sorted according to their priority values. (Event dispatching as described here is consistent with the observer/ publish-and-subscribe patterns published elsewhere.)




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