• No results found

Having dealt with the basic operations-related issues, there are still some specific things to discuss: polymorphism, signals, and interfaces.

Polymorphic Operations Recall from Chapter 7 that some operations are polymorphic: they use the same name for different behavior. Overloading uses the same operation name but a different signature (parameters), while

overriding uses the same signature in a subclass. Overloading is mostly cosmetic, giving you a way to make commands look the same even though they differ based on the objects. Overriding works with dynamic binding to provide a real advantage: the ability to call an operation without knowing what kind of object you're calling. Figure 11- 7 shows a portion of Figure 11-1, from the Person subsystem, that illustrates overriding operations.

Figure 11-7: Overriding Operations in the Identification Hierarchy

The abstract classes Identification and ExpiringID both specify abstract operations. The leaf classes implement these operations, overriding the abstract operations with concrete ones. This lets you create a leaf object but refer to it as an Identification or ExpiringID, then to call GetJurisdiction or whatever without needing to know which specific, concrete class the object really is.

Most relational databases do not support any kind of polymorphism, with one exception of which I'm aware. Oracle7 PL/SQL supports overloading in the procedures of a package. You can provide several procedures with the same name as long as the types of objects you pass through parameters are different in type or order (that is, a different type signature for the operation). The primary reason for designing these kinds of overloaded program units is to provide alternative parameter lists for slightly different behavior while maintaining the same name. Using overloaded procedures, you can leave out "optional" parameters, or you can reverse the parameters so that the programmer doesn't need to remember the order. Again, these are cosmetic changes that don't yield a tremendous amount of productivity increase, but it's a nice feature.

For example, say the commonplace book image processing subsystem has the ability to compare fingerprints, handwriting, and facial images to libraries of fingerprint and handwriting records and mug shots. This service

provides consulting detectives with the ability to use evidence gathered at the crime scene to identify people that might have something to contribute to a solution to the problem.

Having once spotted my man, it was easy to get corroboration. I knew the firm for which this man worked. Having taken the printed description, I eliminated everything from it which could be the result of a disguise—the whiskers, the glasses, the voice, and I sent it to the firm, with a request that they would inform me whether it answered to the description of any of their travelers. I had already noticed the peculiarities of the typewriter, and I wrote to the man himself at his business address, asking him if he would come here. As I expected, his reply was typewritten and revealed the same trivial but characteristic defects. The same post brought me a letter from Westhouse & Marbank, of Fenchurch Street, to say that the description tallied in every respect with that of their employee, James Windibank. Voilà tout! [IDEN]

The parameters to the Compare operation on these classes will be wildly different. For example, the Compare operation for fingerprints might specify that the print is a thumbprint or a fingerprint, or it might specify that the image contains two or more adjoining prints. A typewriter image might contain parameters that identify the text of the image or the probable make of the typewriter. The facial image might contain a list of features to ignore in the comparison, to eliminate disguises. All the classes have several Compare operations, none of which have the same parameter types or signatures. When the calling operation compiles the Compare call, the compiler decides which method to call based on the parameter signature.

True overriding, dynamic binding, or virtual operations simply don't exist in the relational world. If your UML model depends on these, what do you do in your relational program units to implement your model?

The issue with overriding behavior is that the runtime system needs a lookup table to decide which procedure to execute. In C + +, every class with virtual methods has a vtable, a table of function pointers that permits the C++ runtime system to call the overriding method rather than a single method that the compiler determines when you build your system. You have a simple choice in relational systems: either build a dynamic binding capability in your procedural language or just ignore it. My advice: keep it simple. I've seen many work hours spent on systems to emulate OO dynamic binding and inheritance in the context of C programming, for example. It usually works, sort of, but always at a cost— the cost of maintaining outrageously complicated code, the cost of explaining what that code does, and the cost of telling your boss why this is really a good thing even though it reduces productivity.

So, where does that leave you with your UML design? You have to rename your overriding operations with unique names and call them in the appropriate places. That usually means more ornate conditional code such as case statements or if-then-else conditionals that test the types of the objects involved and decide which procedure to call. Take the GetJurisdiction operation, for instance. That operation returns different information for each class, as the type of jurisdiction varies widely with the classes. Counties issue birth certificates, nations issue passports and national IDs, and states issue driver's licenses. In an OO system, all these operations override the same abstract parent. In Oracle7, that's not possible, so you must provide different stored procedures for each operation. The DriverLicense package has its GetJurisdiction, and the Passport package has its jurisdiction. In this case, you use the package name space to distinguish between the two functions. When you refer to it in PL/SQL code, you preface the operation name with the package name: DriverLicense. GetJurisdiction().

In other procedural languages that don't have packages, you would have to use a naming convention to distinguish the procedures. You could, for example, preface the operation name with the class name:

DriverLicense_GetJurisdiction(). Often it is advisable to come up with a unique abbreviation for the class name to shorten the total name to get it within the limitations of SQL identifier length for the RDBMS: DrL_GetJurisdiction, for example.

Note

You should realize that despite your creation of these methods, you are not getting any of the benefits of polymorphism. Your SQL or PL/SQL code must know exactly what kind of object you are dealing with in order to call the method, so you don't get the benefits of dynamic binding through inheritance.

Signals If you design a UML operation with the «signal» stereotype, it means that the operation responds to an event of some kind. This corresponds to a trigger, as long as you limit the events to the specific set of trigger events. Most RDBMS products now support triggers, though there is no standard ANSI syntax for them in SQL-92. The SQL3 standard defines the CREATE TRIGGER statement (Figure 11-8).

The event that fires a trigger is some point in a database system operation. Figure 11-8 defines the standard events for «signal» operations in a database as the various combinations of the <trigger action time> and the <trigger event>: ƒ BEFORE INSERT ƒ AFTER INSERT ƒ BEFORE DELETE ƒ AFTER DELETE ƒ BEFORE UPDATE

ƒ AFTER UPDATE

Figure 11-8: The <trigger definition> SQL3 Syntax

You can therefore define «signal» operations in your persistent classes with these names, suitably transformed into whatever naming convention you use: "BeforeInsert," for example. The "before" operations execute before the corresponding database operation (insert, delete, or update), while the "after" operations execute after them.

Note

When you transform the «signal» operation into a database trigger, you will need to use the procedural language available in your system, such as Transact/SQL for SQL Server and Sybase, PL/SQL for Oracle7 and Oracle8, or a programming language such as C for DB2. Each system has its own tips and tricks relating to triggers, such as how to refer to old data versus the changed data in an update trigger, so use the documentation for your system to figure out the best way of moving the code from your data model into your database schema. As with any operation, you can use the procedural language from your target DBMS or some neutral pseudocode or programming language to represent the trigger implementation in your data model.

The other parts of the trigger definition syntax vary from product to product. The SQL3 standard contains the REFERENCING clause that lets you refer to both the old values and the new values in an UPDATE action. The action itself is an SQL procedure sequence. In Oracle7, the equivalent is a PL/SQL block, for example.

Interfaces Interfaces are a special case of class—a class with no attributes, just operations. A persistent interface is thus somewhat of a contradiction in terms, particularly for a relational database. You should consider an interface a simple notation for polymorphism (see the previous section on "Polymorphic Operations"). That is, if a persistent class realizes an interface (see Chapter 7 for the notation), you need to implement stored procedures corresponding to the interface operations if they are appropriate for DBMS-server execution.

So, for example, if you design the Identification hierarchy as an interface hierarchy as in Chapter 7 (Figure 7-9), you need to implement the GetID operation for each table you generate from the classes that realize the Identification interface. Again, you don't get the benefits of full dynamic binding using interface realization, but you do get to implement the design to the fullest given the relational tools at your disposal.