Previous Section  < Free Open Study >  Next Section

11.8 Returning to the Domain of the ATM

The actual processing of a transaction with respect to an account is fairly straightforward. Each transaction will check account information to ensure the viability of the transaction, update any information that can be updated (e.g., withdraw), and record any necessary audit trails that may be needed later (e.g., deposit). The transaction process method then returns good or bad to the Bank, which in turn returns this information to the ATM object. Recall that the actual sequence of events includes a return to an ATM proxy, which packages and ships the return value to a Bank proxy using a network. The Bank proxy then unpackages the return value and returns to the actual ATM object. As far as Bank and ATM are concerned, the proxies are the actual objects. The distributed processing has been buried in the implementation details.

The question now is, "What does an ATM do if the transaction is valid?" The problem here is that the ATM has the ability to do anything any transaction could want, but it does not know which transaction it has. The transaction knows (via a polymorphic call) what it needs to do but does not have the objects needed to do it. What do we do? The solution is for the ATM to tell a transaction to post-process itself (polymorphically), passing itself as an explicit argument to the method. The polymorphic call allows the flow control to figure out which transaction it has and what needs to be done; the ATM parameter allows the post-process method the ability to accomplish its goals. In this case, these goals involve dispensing cash to a customer. (It is helpful to note that each transaction also has a preprocess method; for example, deposits take an envelope using the deposit slot object, withdrawals check to be sure the cash dispenser has enough cash, etc.)

This solution bothers a lot of designers because we have a circular uses relationship. The ATM uses Transaction, and now Transaction uses ATM. While I feel uncomfortable with such a construct, it is the cost of eliminating explicit case analysis in the methods of the ATM. It is helpful to note that we are adding a new uses relationship between the Transaction and ATM classes, but this is no more of a concern than the addition of any other uses relationship. Avoid the temptation to say that we only need to pass the cash dispenser to the post-process method of the withdraw class. Keep in mind that the ATM does not know which transaction it has; if it were a balance query, it would require a printer, not a cash dispenser. I have seen solutions where the designer passes a number of ATM pieces to the polymorphic call, letting the actual method determine which of the pieces it will use. This solution is appropriate when we can guarantee that all of the transaction types use a proper subset of the ATM's pieces. If we want to be general and extensible, then we should pass the whole ATM to the method.

This often leads new designers to argue that the ATM now has a method in its public interface which will allow a user to dispense cash without any checking. Keep in mind that "allow a user" refers to a software developer, not an end user of the ATM machine. We are not arguing for a large red button on an ATM machine that, when pressed, mindlessly dispenses cash. This type of argument comes up very frequently when designers confuse software objects with the physical world. I have had attendees of courses worry about the fact that some BankSafe class has an open method. They ask, "How do I prevent a bank teller object from sending the safe object the open method, but allow the bank president object to send that method?" They feel a bit silly when I tell them to not code such a message send from within any method on the teller class. These are software objects; they cannot do things you do not code.

An interesting, related point to the confusion between users of classes and end users of systems came up in a course I was lecturing at a large telecommunications company. Should object-oriented languages support a feature by which a class can limit the access to certain parts of its public interface to a given list of classes? That is, should a BankSafe class be allowed to state that anyone can execute its close method, but only BankPresident, BankVicePresident, IRSOfficer, and Police objects can execute its open method? My initial reaction was to answer no. I argued that this would hinder software reuse in that I would need to modify the class definition if I decided that I wanted to send a restricted message to a class. I further argued that such restrictions were application semantic constraints that should be handled by the application designer. However, the company with which I was dealing was building a large application (millions of lines of code) consisting of an object-oriented framework and many applications to be built on top of it. They argued that within the classes of the framework were many operations in the public interface that were to be used exclusively by the other classes within the framework, not the applications. Other parts of the public interfaces of the framework classes were to be used by both the framework classes and the application writers. Due to the size of the development effort (hundreds of developers on two continents and in three countries), they argued that to convey the necessary aplication-level semantic constraints without language-level support was impossibly complex. Application developers were bound to make mistakes in their use of operations. I agree with this assessment. The trade-off is between communicating application-level semantic constraints versus ease of software reusability. They could have solved the problem by creating an object-oriented API (application interface) that mirrored the classes in the framework. For each class X in the framework, the API would have an APP_X that looked exactly like X except it would have a reduced interface. Each method in APP_X would simply delegate to the exact same method in X. The problem here is keeping each X and APP_X pair in sync with each other, another trade-off to play against the other two solutions.

    Previous Section  < Free Open Study >  Next Section