Object Thinking (DV-Microsoft Professional)

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:

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

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.

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?

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).

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.

 

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

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:

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.

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.

Figure 8-3: Object classes from the mortgage trust application introduced in the preceding chapter.

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.

Figure 8-5: Sides 1 and 5 of the objects in the ATC example introduced in the preceding chapter.

Figure 8-6: Responsibilities for the objects in the mortgage trust application (object cube side 1).

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:

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.

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:

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.

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:

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.)

Категории