Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

User interfaces for object-oriented systems, Part 4: Menu negotiation

A scalable architecture for building object-oriented user interfaces

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Before we turn our attention to the main subject of this month's Java Toolbox -- menu negotiation -- it seems, judging by numerous reader inquiries, that I have a bit more explaining to do about the visual-proxy architecture that I discussed in the September column.



Operations on attributes

The first recurring question had to do with the notions of Employees and their salary attribute: How can you sum all the salaries of all the employees in some department without violating encapsulation? Or, to put it another way, why don't you have to get a number out of the Employee class to perform this operation?

My answer is that you don't get the value of the salary from the Salary object. You might have a salary() method that returns a Salary object that represents the salary, but the implementation of the salary is still hidden inside an object. The main purpose of the auxiliary Salary object is to remove the salary-manipulation functions from the main Employee interface and put them into a class of their own. That is, rather than give the Employee a print_your_salary() method, you'd give the Salary a print_yourself() method. (The salary would also support methods to do other salary-related operations, of course.) You could then print an employee's salary with either:

some_employee.print_your_salary();
                                      


or

some_employee.salary().print_yourself();
                                      


Adding its salary to a sum is also a reasonable request to ask of Employee in some situations; both of the following are workable object-oriented solutions, since neither exposes the way in which salaries are implemented:

some_employee.add_your_salary_to(
                                       Salary the_sum )
                                       {   the_sum.add( my_salary );
                                       }
                                      


Or, if you had resorted to a get method:

the_sum.add( some_employee.salary() );
                                      


Interactions between proxies

The second issue readers brought to my attention was: How do you handle interconnected controls on a form? For example, what if the status of some checkbox determines which values are valid in another proxy entirely? The quick answer is that the interaction happens at the model (abstraction) level, and the proxies take care of themselves.

The figure below demonstrates two common situations. On the left, a model-level object has displayed proxies for two attributes, one as a checkbox and the other as a field of some sort. When the user clicks on the checkbox, a model-level listener (manufactured when the proxies are created) notices that the checkbox has changed state and enables the associated field.

The more interesting example is on the right side of the figure. Here, there is a complex interaction between proxies for three objects of different classes: an instance of Company called employer, an instance of Group called division, and an instance of Person called employee. The proxy for the Company displays a list of divisions. It can do this without any communication with the rest of the system because of the qualified association. (Think of the qualifier as specifying a hashtable of Group objects, keyed by division Name. The Company can create a UI displaying all the division names simply by calling getKeys() on the hashtable.)

Now look at the sequence diagram shown in the comment on the figure. When the user selects a division's name, the model-level employer object looks up that division in the hashtable, then asks the division to display its employees. The division turns around and iterates across its list of employees, asking each one for a proxy, which division displays on some UI (a list, for example) that it's creating. When it's done with the traversal, it displays the list. The UI seems to be unified in that selecting a division causes a list of employees to change, but it's really model-level communication that's going on, not communication between proxies. Of course, the Bag wrapper I discussed last month would simplify the implementation considerably, since it would take care of creating the lists.

Intercommunication between proxies



Tabbing order and focus shift would be handled in much the same way. Either the model-level entity that creates the proxy would establish these behaviors when it created the proxies, or the associated control object would do so. For example, in the Form class that we looked at two months ago, the Form object could easily establish a tab ordering that would follow the order in which Element objects appeared in the form description. Invariant fields would not be put into the tab ordering at all.

Data validation and cancel buttons (transaction-based proxies)

The final question that kept coming up in your letters was also one of interaction: What about data validation upon close, rather than on the fly? What about a cancel button undoing any changes requested by the user after the form displayed?

This one is more complicated. In order to make the architecture more clear, my earlier article presented a simplified version of the system that I actually use, and though I don't want to rewrite the article, I can at least explain the more sophisticated architecture. Rather than returning a simple JComponent, you could rewrite the User_interface to support transaction-based messaging, as follows:



The Transaction interface supports begin() and commit() operations. (The former effectively activates the proxy; the user can then interact with it, but the results of that interaction are not sent to the abstraction-level object until a commit() is issued.) The rollback() method causes the proxy to interact with the model, and should put the model back into the state it was in before a previous commit() was issued. You can use this mechanism for undo operations and the like. The supported_rollback_levels() tells the control object how many levels of rollback are supported. It could return 0 if rollback isn't supported at all.

Given this structure, the control object has choices that it didn't have before, and can easily support the requested features. For data validation on close, the validation is done in the commit() operation, which throws up a dialog box or equivalent if everything's not OK. Cancel is easily implemented, either by not committing until an OK button is pressed, or by rolling back all committed transactions when the Cancel button is pressed.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources
  • A usual, the code to this month's article is available in the "Articles" section of my Web site
    http://www.holub.com
The other Java Toolbox articles in this series
  • The Gang of Four
  • Design PatternsElements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides -- the Gang of Four (Addison Wesley, 1994) http://www1.fatbrain.com/asp/bookinfo/bookinfo.asp?theisbn=0201633612