With a knowledge of CORBA basics--such as the concept of the Object Request Broker (ORB) and the Interface Definition Language (IDL)--along with an understanding of the IDL language, you are ready to develop and deploy CORBA-based applications. You've already gained firsthand experience implementing a simple CORBA server and client. Now you'll design, implement, and enhance a more sophisticated CORBA application. To help you see what's ahead, the following road map is provided:
Although object-oriented technologies have existed for quite some time, the phrase "object-oriented" has gained much popularity (along with buzzword status) in recent years. Indeed, the phrase is often bandied about with reckless abandon, which serves to obscure its real meaning. To further confuse matters, it is used to describe everything from development environments to programming languages to databases.
So what does the term object-oriented really mean? The term seems to be thrown about indescriminately; anything from programming languages to drawing tools might be labeled as "object-oriented." For the purposes of this book, you will be interested primarily in three uses of object-oriented methodology: object-oriented analysis (OOA), which deals with the design requirements and overall architecture of a system; object-oriented design (OOD), which translates a system architecture into programming constructs (such as interfaces, classes, and method descriptions); and object-oriented programming (OOP), which implements these programming constructs. So, for your purposes, object-oriented can be taken to mean the various methodologies, described briefly herein, used to design and implement software. This chapter deals primarily with object-oriented analysis. On Day 6 you'll work with object-oriented design; when implementing system functionality, you'll be using object-oriented programming techniques.
Although this book introduces you to object-oriented analysis, design, and programming concepts, it does not attempt to cover these topics in detail. A number of books already written on these subjects provide a definitive introduction to and explanation of these concepts. Time spent familiarizing yourself with these concepts would be time well spent.
The Unified Modeling Language (UML) is a powerful tool for expressing object-oriented designs. Developed by Rational Software Corporation, UML is an evolution of previous modeling languages and techniques. A description of UML, along with a set of links to UML-related resources, appears on Rational's Web site at http://www.rational.com/uml/index.html.
UML is an evolution of previous modeling languages and techniques. Prior to UML, many object-oriented methodologies existed. Of these, the three major methodologies included Grady Booch's Booch 1993 method, Jim Rumbaugh's Object Modeling Technique (OMT) method, and Ivar Jacobson's Object-Oriented Software Engineering (OOSE) method. In October 1994, Booch and Rumbaugh joined forces to unify their methods, resulting in what was called the Unified Method 0.8 in October 1995. Around that time they were joined by Jacobson, merging the OOSE method with Booch and Rumbaugh's work to form UML 0.9 in June 1996. The UML Partners consortium--consisting of companies such as Digital, Hewlett-Packard, IBM, Microsoft, Oracle, Rational, and Unisys--was then formed to refine UML even further, resulting in UML 1.0 in January 1997. The UML 1.0 documents were submitted for standardization to the Object Management Group (OMG)--the organization responsible for the specification of CORBA standards.
The Unified Modeling Language is a highly visual language; in addition to words and text, it also consists (and in fact primarily consists) of graphs and symbols. Perhaps one of the most important diagrams you will encounter in object-oriented analysis and design is the class diagram, which in turn consists of notations for classes, associations, and inheritance (among other things, but these are the three aspects you'll study here).
One important element of the Unified Modeling Language (or any modeling language, for that matter) is the class diagram. The class diagram, sometimes called (incorrectly) an object diagram or object model, describes classes and their relationships to other classes in the system. The class diagram specifies only static relationships--how classes are related to each other--and not dynamic relationships, such as when objects are created or invoke services of other objects.
New Term: A class diagram graphically depicts the relationships between classes in a system. Depending on the level of the diagram's scope, it may also describe the attributes and operations provided by each class.
The class diagram is one of the most important elements of an object-oriented methodology. It is essential to the understanding of a complex system architecture and provides a great deal of insight into a system design.
Naturally, the existence of the class diagram implies the existence of the class.
As you might expect, the class in UML is analogous to a class in an object-oriented
programming language such as Java or C++. A class has a name, zero or more attributes,
and zero or more operations. Think of attributes as member data and operations as
member functions or methods. In a class diagram, a class description can take on
one of the forms shown in Figure 5.1.
Figure 5.1. UML class descriptions.
Figure 5.1 depicts three examples for the representation of a class. In the first
example, only the class name is visible. This form is suitable for a class diagram
that focuses primarily on the relationships between classes. For example, an extremely
complex class diagram benefits from this type of simplification, especially if the
diagram is only to be used to provide an overview of an entire system. The second
example shows the class name, its attributes, and its operations, but attributes
and operations are listed by name only--types and parameters are omitted. The third
example shows a fully embellished class description, with class name, attributes
and their types, and operations with their parameters and return types. These types
of class descriptions are useful when detailed information about a system and its
classes is required.
A class description can also provide visibility modifiers for its attributes and operations. The visibility modifier, which is optional, immediately precedes the attribute or operation that it describes. (When no visibility modifier is given, the attribute or method is usually assumed to be public.) A description of each of these modifiers appears in Table 5.1.
Symbol | Description | Meaning |
+ | Public attribute/operation | Public attributes and operations of a class are available to that class and to any other class. |
# | Protected attribute/operation | Protected attributes and operations of a class are available only to that class and its subclasses. |
- | Private attribute/operation | Private attributes and operations of a class are available only to that class, excluding even subclasses. |
/ | Derived attribute | A derived attribute is an attribute that is dependent on another attribute. For example, although a person's age can be considered an attribute, it is dependent on the current date and the person's birth date. Derived attributes can also be public, protected, or private. |
$ | Class attribute/operation | A class attribute or operation can be accessed without an instance of the class. Class attributes and operations are analogous to static class members in C++ or Java. Class attributes and operations can also be public, protected, or private. |
A class typically does not exist and act within a vacuum; generally it will interact with other classes as well. Thus, it can be said that a class has relationships to other classes. UML refers to these relationships as associations. A class has an association with another class if it uses services of that class in some way. Optionally, the association can be given a name; additionally, the roles that each class plays in an association can be given names as well. Figure 5.2 illustrates the notation for an association, which is indicated by a line drawn between two classes.
New Term: An association between two classes means that the classes are somehow related. Typically this means that one of the classes uses services of the other or has a member which is an instance of the other class.
An association can also have multiplicity, meaning that a certain number of one class can be associated to a certain number of the other class. For example, Figure 5.2 indicates that a Customer can hold more than one Account. Furthermore, an Account can be held by more than one Customer (as is the case with joint Accounts). Finally, an Account can be owned by only one Bank. Essentially, there are three types of multiplicities in relationships:
Figure 5.2. UML class associations.
New Term: Multiplicity refers to the number of classes involved in
an association. Examples of multiplicity are one-to-one, one-to-many,
and many-to-many.
Specifying multiplicity is optional; this information is sometimes omitted for the sake of clarity in high-level class diagrams. However, you need to specify multiplicities of associations before implementing your design, as the details of the implementation depend on this multiplicity information.
Inheritance is actually a special case of an association. It has the same meaning as you would expect in an object-oriented language; a class that inherits from (or derives from) another class (recall from Day 3 that this is referred to as the superclass) inherits the nonprivate attributes and methods of that superclass. Again recalling from Day 3, remember that the derived class can be substituted wherever its base class is required (as a parameter to a method call, for instance); this behavior is known as polymorphism.
In UML, inheritance is represented as an arrow drawn from the derived class to its base class. UML supports the notion of multiple inheritance as well; in this case, arrows are drawn from the derived class to each of its base classes. Examples of UML expressions of inheritance associations are illustrated in Figure 5.3.
Again, it is far beyond the scope of this book to discuss object-oriented analysis
and design methodologies in depth. However, a good first step in the analysis phase
is to identify the objects, or classes, that compose the system. Many objects in
the system are easy to identify; one method is to first write a description of the
system and its function. When the description is complete, review it and look for
nouns. When you encounter a noun, chances are that it will represent an object in
the system. For example, in the sentence "Customers hold accounts in a bank,"
potential object candidates are Customer, Account, and Bank.
This process, sometimes called object discovery, is very useful in understanding
the scope of a particular system.
Figure 5.3. UML inheritance associations.
After the candidate objects have been identified, you determine the relationships
between the classes. These relationships are often expressed in the form of verbs
in the system description. In the preceding example, for instance, you see that Customers
hold Accounts, suggesting a relationship between these two classes. Furthermore,
you can see that Accounts are part of a Bank, although it is not
clear precisely what the relationship is between an Account and a Bank.
Associations between classes don't have to be named, although named associations
often provide additional insight into the design of a system.
Note:If you infer from this process that classes ought to be named with nouns and associations ought to be named with verbs, you are correct. This naming scheme is a generally accepted convention of object-oriented analysis. Another convention is to give singular names to classes, for example, Account rather than Accounts.
After the classes and their associations have been identified, you'll want to spend some time determining the attributes and operations within classes. This requires more thought than the first two steps, and chances are you won't get it right the first time. In fact, the entire process is an iterative one. While identifying relationships between classes, you might discover new classes, or while determining operations on objects, you might discover new associations or new classes. In fact, your design might change drastically from start to finish; this is a normal aspect of software design. Multiple iterations of this process help you create a robust design, and creating a solid design early on will help you avoid headaches later on in the development phase. (It is often observed that the later in the development process a change needs to be made, the more costly that change will be. Therefore, it is to your advantage to spend a good amount of time refining your design.)
UML is a very broad-reaching tool that encompasses not only the static design of a system (such as the class diagram) but also the dynamic design (including use cases, state transition diagrams, and other tools). Because this book can barely scratch the surface of UML and its functionality as a design tool, you should explore either the Rational Software Corporation Web site (provided at the beginning of this section) or one of several other sources for more information on UML.
The next several chapters center around a single example--an electronic banking system. In this chapter, you'll use object-oriented analysis to define the objects in the system and create an application object model. In subsequent chapters, you'll implement the system's basic functionality and then implement additional capabilities and robustness. In the end, you'll have built a complex (although still trivial by enterprise application standards) CORBA application. Along the way, you'll discover some of the issues involved in building such a system.
The Bank application supports electronic banking. It allows for multiple banks, multiple accounts (checking and savings), multiple customers, opening and closing accounts, and the withdrawal, deposit, and transfer of funds between accounts. (More capabilities are added in later chapters, but the basic Bank application begins with this functionality.)
The first step in designing any system is to determine what the system needs to do. Depending on the nature of the application, this process can involve meeting with customers and/or potential users of the system, conducting market research, or just providing a solution to a particular problem. Requirements often have to be refined or clarified later, so don't be surprised if you find yourself revisiting this step again.
For the Bank example, the capabilities of the basic system are defined as follows:
Notice that each line item describes one capability; for instance, the capabilities to create and delete accounts compose separate line items. This convention facilitates the development of testing requirements because the functionality described in each line item is individually testable. Note also that when analyzing system requirements, it is generally good practice to ensure that each capability is indeed testable. For example, a requirement such as "Must be easy to use" is subjective and so probably not testable. Avoid vague requirements like this; a more useful set of requirements would list specific user-interface features that one might consider "easy to use."
Now that you have arrived at a set of system requirements, you are ready to determine what objects exist in the system. As suggested previously, you do this by scanning the application description and requirements for nouns. Nouns that you'll encounter are bank, account (specifically, checking account and savings account), customer, and funds. All these are candidates for inclusion in the object model (or class diagram). One way to determine whether a class should be created for a candidate is to ask yourself this question: Is there an identity or behavior associated with this object? If the answer is yes, then the candidate should be an object. Try this test on your list of candidate objects:
From this analysis, you can see that the system will include at least three major classes: Bank, Account, and Customer. Now you need to focus your attention on the attributes and behaviors of such objects.
Several of the system requirements suggest behaviors that should be included in the Bank class:
Note:In a C++ or Java application that does not use CORBA, you can very well provide a static method of Bank that would return a list of Bank objects, which is a reasonable approach. However, because CORBA objects don't support static methods, an alternative approach--such as those mentioned previously--is required.
Note:You will often encounter situations like the preceding one, where there is no clear answer as to where certain behavior should be placed. Use your best judgment, or sometimes even make an arbitrary decision.
Additional attributes of a Bank might prove useful; for instance, the Bank should probably have a name and perhaps an address. For this application, these attributes will be kept simple:
name : string address : string
BankServer is a class that was unanticipated in the preliminary analysis but popped up during your analysis of the Bank class. The BankServer class is very simple, its only job being to provide visibility to Bank objects. In order to provide this capability, the following operations are required: Register a Bank with the BankServer, unregister a Bank from the BankServer, and list all Banks currently registered with the BankServer. More formally, these operations are defined as follows:
registerBank(bank : Bank) : void unregisterBank(bank : Bank) : void getBanks() : Bank[]
For the purposes of this application, no other capabilities are required of the BankServer class.
The next class you will consider is the Account. This class implements a great deal of the Bank application's initial functionality. Here is how the Account class meets the requirements of the system design:
These requirements suggest that specializations of the Account class will exist. In particular, you will use CheckingAccount and SavingsAccount. Although one could argue that the account type is actually an attribute of the Account class, for the purposes of this application, the CheckingAccount and SavingsAccount will be subclasses of Account. This approach makes sense because a SavingsAccount has attributes and behaviors not applicable to a CheckingAccount, and vice versa. Because these classes exhibit different behaviors, it is probably better to create separate classes for each of them.
Finally, the Account should probably contain some additional attributes to make it interesting. First, it should have an account number so that it can be identified by a human customer (and also to identify the account on printed checks); it would also be nice to retain the creation date of the account. Note that these capabilities were not spelled out in the requirements, so you could technically do without them. However, they are likely to become useful sooner or later, hence their inclusion here:
accountNumber : string creationDate : date getAccountNumber() : string getCreationDate() : date
Notice that the operations listed here are redundant with the attributes. This is in keeping with the typical practice of making attributes private and allowing access to those attributes through accessor methods. Although you can choose not to follow this convention for non-CORBA applications, access to attributes of CORBA objects always takes place through accessor methods. Remember, though, that one advantage to this convention is that it allows you, if you so desire, to restrict external access to object attributes to reading only. Such is the case in this example, as only accessors--no mutators--are provided. This ensures that attributes that should be immutable--such as creation date and account number--cannot be altered.
CheckingAccount, which derives from Account, provides additional attributes and behaviors. However, at this point in the application design, CheckingAccount adds nothing new to Account.
SavingsAccount, which also derives from Account, provides additional attributes and behaviors as well. In particular, a SavingsAccount has an associated interest rate, along with an accessor and mutator for this attribute:
interestRate : float getInterestRate() : float setInterestRate(newRate : float) : float setInterestRate()
returns the old interest rate as a convenience to the user.
The Customer in this application is a relatively simple class because it is mostly a consumer of services offered by other classes. Only one of the system requirements falls to the Customer class's responsibility.
Additionally, to make the Customer interesting, a few attributes will be added to provide the Customer's name, Social Security number (as a means of identification), address, and mother's maiden name (for security reasons and just plain old tradition):
name : string socialSecurityNumber : string address : string mothersMaidenName : string
To keep things simple, the address attribute is simplified into a string rather than street address, city, state, ZIP code, and so on. However, providing a separate Address class (which could possibly have derived classes as well) might not be a bad idea for a more robust system.
Notice that nowhere in the previously described classes is there any mention of attributes whose purpose is to uniquely identify the object. (The possible exceptions are the Customer's Social Security number and the Account's account number, which will be discussed in a moment.) This is because object-oriented analysis makes the assumption that objects implicitly have unique identity, making an identity attribute redundant. Therefore, at the analysis level, classes should not contain attributes that exist solely to identify the object.
There are exceptions to this rule. Most notably, a Customer has a socialSecurityNumber--an attribute that exists primarily to uniquely identify the Customer. However, this type of identity attribute is often used because it corresponds to a real-world concept. A person, for example, has a unique identity simply by virtue of the fact that he or she exists. The Social Security number, because it is a ubiquitous method of identifying people (at least in the U.S.A.), is often convenient to use in software applications.
Other identification attributes exist as well and are perfectly legitimate for use in an application design. Another example is the account number in the Account class. Account numbers are often used to identify an account on a printed check or on a statement sent to the customer.
The key to understanding when identity attributes are appropriate is this: An artificial identity attribute has no place in a class description, whereas an identity attribute that exists in the real world--such as a socialSecurityNumber--is acceptable and even useful.
Now that you've identified the components (classes) of the system and described their attributes and behaviors, you're ready to put them together into a cohesive class diagram. The class diagram shows not only the classes themselves (including, if desired, their attributes or operations) but also the relationships between classes.
Figure 5.4 shows the initial class diagram for the Bank application. (You'll be making changes to the class diagram from time to time as the system design evolves throughout the next few chapters.) Notice that this diagram introduces the UML notation for class inheritance; the SavingsAccount and CheckingAccount classes both inherit from the Account class.
Again, it is stressed that the analysis and design phase is an iterative process.
You will often find yourself going back and tweaking various aspects of the design
as you discover features that were left out or as you learn (stumble across) a more
elegant way of doing things. Revisiting and making changes to work you've already
completed is quite normal in this stage of the game. Strive for the highest possible
quality system design; when you begin implementing the system, sweeping changes to
the design become much more costly. Better to spend more time on it now, during the
design phase, when making changes is much cheaper.
Figure 5.4. The Bank
application class diagram.
Of course, there is much, much more to object-oriented analysis and design than is covered here. Also, a very important part of object-oriented methodology is the development of use cases, which describe scenarios in which various parts of the system interact. These scenarios contain actors, such as a user or another object, that act on other objects. Use cases describe how an actor interacts with the system, what the results are, and the order in which these events occur. There are many possible use cases for a single scenario; for example, for a given dialog box, there might be a use case for when the user enters valid data and a separate use case for when the user enters invalid data.
Another powerful tool of object-oriented analysis and design is the design pattern. Design patterns can be thought of as building blocks for more complex object-oriented constructs. For instance, one common design pattern--one with which you might already be familiar--is the model-view pattern. In this pattern, one object, called a model, represents a piece of data or concept. Another object, called a view, tells a model that it wants to receive updates whenever the model's state changes. An example of a model class is a TemperatureSensor, which monitors the outdoor temperature. A view class, such as TemperatureDisplay, might be a view of the TemperatureSensor class, meaning that when the TemperatureSensor detects a change in the outdoor temperature, it notifies the TemperatureDisplay of the change. The TemperatureDisplay obtains and displays the new temperature information. A model might have multiple views, as well; in this case, the TemperatureSensor notifies multiple TemperatureDisplays when the outdoor temperature changes.
A number of excellent books have long been available on subjects such as use cases, design patterns, and other important object-oriented concepts. Again, you are encouraged to explore these topics in depth; knowledge of such concepts pay off in designing any type of system--not just CORBA applications.
In this chapter, you took what was essentially a crash course in object-oriented analysis and design. You learned a bit about the Unified Modeling Language, its notation, and the basic methodologies involved. You applied these concepts to the design of a basic Bank application. In the analysis and design phase, not much attention is given to the details of implementation; in fact, it is recommended that you avoid implementation details at this stage of application development. A system design that does not depend on such details enjoys greater flexibility than a design that is dependent on the details of a particular implementation.
In the analysis and design phase, you performed three major steps:
In the final part of the design phase--on Day 6, "Implementing Basic Application Capabilities"--you'll translate the system design into an IDL specification. The IDL will be used as--you guessed it--a baseline for the system implementation. You'll then proceed to do exactly this--implement the system's basic capabilities. Future days will be spent enhancing the basic functionality.
The following section will help you test your comprehension of the material presented in this chapter and put what you've learned into practice. You'll find the answers to the quiz and exercise in Appendix A.
Modify the system design so that a Bank consists of Branches, each of which owns some of the Customer Accounts. Draw the class diagram for the modified design.
© Copyright, Macmillan Computer Publishing. All rights reserved.