We use cookies to distinguish you from other users and to provide you with a better experience on our websites. Close this message to accept cookies or find out how to manage your cookie settings.
To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure [email protected]
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
Objects are good. Patterns are good. Networks are good.
These statements have almost become IT axioms in the last few years. I'm not going to buck the trend here. Objects are indeed good. Networks and patterns help to make them even better. I'm going to show a relatively easy way to link these three things together.
Well designed object-oriented applications share a common topology (see Riel, A. Object-Oriented Design Heuristics. Addison-Wesley, 1996.) By topology I mean the natural features of an entity and their structural relationships. A well-planned object model will spread out the application logic within a system. Related data and behavior will be kept in logical, isolated units. Structuring applications in this way promotes reuse, facilitates comprehension, and minimizes the effects of change.
One side effect of partitioning a system in this way is the need for these “logical isolated units” to communicate with each other. Using controller classes to organize the communication is not a viable solution because it concentrates the system's intelligence in a single place. How can we share information without cluttering our design?
The Observer pattern (see Gamma, E., R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.) provides a possible answer to our question. In the applicability section of the pattern description we find that Observer is a good way to “notify other objects without making assumptions about who these objects are.
The java developers' slogan is “100% Java,” meaning “Don't taint your Java software by incorporating non-Java components.” Yet, Java itself is neither 100% pure object nor 100% pure object-oriented; it is “tainted” with components of the procedural programming paradigm. The Java designers have borrowed many ideas from diverse sources—other object-oriented languages, especially C++ and Smalltalk, design patterns (see Gamma, E. et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995), and basic object-oriented design principles—picking and choosing good ideas from each. And they've done a truly fine job. However, at one particular juncture, I believe the language architects made the wrong choice, and that was the decision to incorporate non-object primitive types into the otherwise uniform object-oriented language model.
There are two types of types in Java. By primitive types, I mean elementary variable types such as int, boolean, float, long, short, and so on. These are sometimes referred to as “built-in” types. The other type of type is objects: a variable can be declared to be of type Object or String or any other class. Referring to the two types of types as metatypes, we have primitive and object metatypes (the truth is, the two metatypes are primitive and reference, which includes not only objects, but interfaces and arrays—more on why they are “reference” types later). By incorporating both metatypes, Java is not populated purely with objects.
TheJava Report published its first issue in March 1996. This was a real accomplishment when you consider that it was only on May 23, 1995, that John Gage, director of the Science Office for Sun Microsystems, announced Java to the world at SunWorld. Later in 1995, Sun released the initial Java Development Kit (JDK) and a Java enabled Web Browser called HotJava. The rest is history, as Java is now firmly entrenched in the computing industry. Many of us who saw demonstrations of Java in 1995 knew that it was something new, something different, and something not to be ignored. Those behind the Java Report knew this too, and we have been reporting on Java ever since.
In his first editorial for Java Report, the original Editor-In-Chief, David Fisco, wrote:
The Java community is becoming broader every day, encompassing CIOs, information technologists, market professionals, programmers, multimedia designers, educators, managers, and even hobbyists. … However, many CIOs, developers, and even software experts are having a hard time getting a handle on Java. Some have said that it's just a neat way to run animations on the Web, others note that Java enables Web-based electronic transaction, and still others tout Java as the Holy Grail that will bring about the $500 PC and change the world of computing as we know it.
Recently, the subject of patterns has become one of the hottest topics in object-oriented development. The most well-known patterns are the design patterns of the Gang of Four (Gamma, E., R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994), fundamental implementation patterns that are widely useful in object-oriented implementations. An important part of most OO systems is the domain model—those classes which model the underlying business that the software is supporting. We can see patterns in domain models too, patterns that often recur in surprising places. I will provide a short introduction to some analysis patterns that I have come across.
I'm not going to do this by surveying the field, or discussing theory—patterns don't really lend themselves to that. Rather, I'll show you some examples of patterns and illustrate them with UML diagrams (Fowler, M., UML Distilled: Applying the Standard Object Modeling Language, Addison-Wesley, 1997) and Java interface declarations.
REPRESENTING QUANTITIES
Imagine you are writing a computerized medical record system for a hospital. You have to record several measurements about your patients, such as their height, weight, and blood-glucose level. You might choose to model this using numeric attributes (see Figure 1 and Listing 1).
This approach is quite common, but may present several problems. One major problem is with using numbers.
Javabeans are reusable software components that are assembled using a visual assembly tool to create Java applets or applications. The JavaBean standard provides a software component architecture for Java that promises to deliver rapid application development (RAD) by enhancing reuse and increasing the abstraction level of software development, enabling even non-programmers to develop Java applets and applications. JavaBeans derive power because one can configure and connect beans to achieve sophisticated functionality without knowing all of the internal details of beans themselves.
In principle, a bean may be used by just knowing “what” it does without necessarily knowing “how” it works. For example, a loan calculator bean can calculate the principal and interest due on a loan. Generally, this is sufficient information to use the bean without having to know the details of the algorithm the bean uses. However, this requires some measure of trust on the part of the person using the bean. To draw again on the loan calculator bean example—if the configured loan calculator bean says we need to make a payment of $550 per month for the term of the loan, we need to trust that this result is correct without meticulously working through the beans implementation to ensure it calculates correctly. Clearly, without this trust the bean loses much of its power as a software component for reuse and RAD, and worse still, fuels the “not invented here” syndrome.
Testing is not closely integrated with development. This prevents you from measuring the progress of development—you can't tell when something starts working or when something stops working. Using JUnit you can cheaply and incrementally build a test suite that will help you measure your progress, spot unintended side effects, and focus your development efforts.
THE PROBLEM
Every programmer knows they should write tests for their code. Few do. The universal response to “Why not?” is “I'm in too much of a hurry.” This quickly becomes a vicious cycle—the more pressure you feel, the fewer tests you write. The fewer tests you write, the less productive you are and the less stable your code becomes. The less productive and accurate you are, the more pressure you feel.
Programmers burn out from just such cycles. Breaking out requires an outside influence. We found the outside influence we needed in a simple testing framework that lets us do a little testing that makes a big difference.
The best way to convince you of the value of writing your own tests would be to sit down with you and do a bit of development. Along the way, we would encounter new bugs, catch them with tests, fix them, have them come back, fix them again, and so on. You would see the value of the immediate feedback you get from writing and saving and rerunning your own unit tests.
A basic problem with most current Java implementations is that they were designed to support very small applet-style programs. The ability to effectively execute very large, computationally complex application programs is not currently an area of strength for Java. However, Java is now being used as a general-purpose language for implementing all types of programming problems including computationally intense applications. The translation and execution techniques used by current Java implementations do not necessarily scale to support the performance requirements of such programs. Static compilation with aggressive optimization is an implementation technique that is commonly used for languages such as C, C++, and FORTRAN but has not been widely used to implement Java. This article identifies some of the reasons for Java's performance problems and examines how static compilation and optimization techniques can be applied to Java to alleviate these problems.
WHY DOES JAVA HAVE POOR PERFORMANCE?
Java's performance problems can be traced to two major sources: the inherent inefficiencies of object-oriented (OO) programming constructs and the use of a virtual machine (VM)-based implementation strategy. After explaining the details of these inefficiencies I show how static compilation technology is able to overcome them.
OO LANGUAGE INEFFICIENCIES
OO programming languages such as Java have been widely promoted as key tools for increasing programmer productivity. The productivity improvements of OO languages are generally achieved through code reuse. Using an OO language such as Java, a programmer needs to write less new code because OO languages support and encourage the reuse of existing code.
Multithreaded programming has been with us for many years and is considered to be a feature that many robust applications utilize. Exception handling is another feature of many languages considered to be necessary for proper and complete error handling. Each of these technologies stands very well on their own. In fact, you cannot pick up a book about Java programming without finding a chapter devoted to each of these topics. Many of these books do a good job defining and describing how to use them properly in your programs. What is missing is the information on how to use these two technologies together effectively. After all, how effective is writing a multithreaded Java program if it is incapable of properly handling exceptions occurring on secondary threads? We will present a solution for effectively dealing with this problem.
To solve this problem, we introduce two new classes and two new interfaces to be used when writing multithreaded Java programs. These classes are small, easy to use and understand, and effectively enable you to handle exceptions occurring on secondary threads.
Writing robust code implies many things. One of them is the proper and effective way in which your program deals with error situations. The approach you take can vary from doing nothing to handling any and all problems. The approach you choose is more than likely dictated by the type of application you are writing.
This is the second of a two-part series on thread separation. The first, published in Java Report's August 1998 issue, dealt with general issues and techniques for addressing them. This installment describes a simple framework to thread-separate servers from the clients that call them. This thread separation is useful if the request being served is a lengthy one, such as to retrieve an image from a large image database. The client may go on doing other things and be called back by the server when the requested task is complete. Thread separation of servers is also useful when (e.g., in a Web server), you have chosen a thread-pooling solution to limit the number of concurrent threads running in your server. All client requests are transferred to one or more controlled server threads for execution. Finally, thread separation is useful when you take advantage of the “liveness” rationale for threading, in effect, making tasks in a system “live,” because it allows them better to embody the behaviors of their real-world counterparts. Thread separation allows different components to each live and run within their own thread or threads, with control and notification between components being as brief and shallow as the designer desires.
PROBLEM DOMAIN
I recently worked on a heavyweight component model that simplified the creation of system servers. Most of the top-level components in our system were built on this model and most of these components had presentation objects that commanded them based on user actions.
Moving to a new language involves some paradigm shifts in how you think about structuring programs and solving problems. Often we think in terms of solutions instead of problems and so ask questions like, “How do I pass a method pointer Java?” instead of “How do I encapsulate a behavior reference in Java?” This is particularly important for C++ programmers migrating to Java, because the similarity in syntax between the languages can lead to the assumption that the language paradigm is identical.
I'll discuss two classes of problems faced by developers. The first is what I call “Conceptual Confusion,” where a user of one language carries their assumptions to another language and then gets confused when their assumptions are invalid. The second class is the desire for new features to be added to the language. This “Creeping Featurism” generally involves adding complexity to the language for the sake of mimicking another language's feature, often no more than syntactic sugar. That is, the proposed feature may reduce the typing without adding to the power of the language.
Let me warn you of my bias: I find that too many features in a language confuse me. I find that a simple language based on a single paradigm provides for less confusion, better maintainability, and quicker code development. You may understand that neat “constant reference” feature, but think of the person who may have to fix, reuse, or extend your code a year from now.
Permutations and combinations appear in problems that have more than one answer, where we want to know what all the possibilities are or just how many possibilities there are. For example, a basketball coach may need to select a team of 5 players from the 10 boys on his squad. How many possible teams is that? Is it more than we can reasonably put in a list? If the coach has statistics on the five starting players from a competing team, can he match up his players with them based on height, speed, and experience? You can address these questions with a handful of algorithms for permutations and combinations that are an important part of a Java developer's toolbox.
COUNTING PERMUTATIONS
A permutation is an ordering of items. For example, we might have three errands to do, with a choice about what order to do them in. If we have to buy groceries, mail a package, and get an oil change, one possible ordering or permutation is {groceries, mail, oil}. Altogether, there are six possible orderings:
groceries, mail, oil
groceries, oil, mail
mail, groceries, oil
mail, oil, groceries
oil, groceries, mail
oil, mail, groceries
We can count these choices algorithmically, without necessarily listing them. Notice that once the errand runner completes one of the three errands, there are always two left. After the errand runner completes two errands, there is always one left.
In our complex world, events are constantly occurring. Any one person is only interested in a very small subset of all these events, so humans have worked out ways of getting just the information of interest, which works to a degree. We may periodically check to see if the event has occurred, or we ask someone to notify us when the event occurs. Often there is more than one source of a particular type of event such as disaster-related events, but we do not typically care about who notifies us, just that the event has occurred. Ideally, we would subscribe to just those types of events in which we are interested and be notified of them when they occur.
Event notification is a useful communication paradigm in computing systems as in real life. This article documents general event notification in the form of a design pattern, which provides a useful way of exposing readers to the important concepts and issues involved in event notification, and provides a language-neutral pattern for implementing event notification in a variety of scenarios. Concurrently, we discuss design issues using examples as appropriate to demonstrate effective use of event notification. Diagrams use the Unified Modeling Notation (UML) (see Unified Modeling Language User Guide, Booch, G. et al., Addison-Wesley, 1997).
After my last article for Java Report (“Tapping the Power of JavaScript,” March 1997), I received a question from a reader asking if there was some way for a Java applet to call a JavaScript function. I knew that JavaScript could call Java funtions, but I wasn't sure if things would work the other way around. So, it was off to the Internet to investigate! I wound up at Netscape's official JavaScript documentation site and here's what I found.…
THE BASICS
First, let's answer the question that started all this: Yes—Java methods can call JavaScript code, and JavaScript code can call Java methods. However, as you might expect, there are some exceptions and restrictions, and those depend on which browser you are using (we'll discuss these a bit later on).
The second question you might have is: “Why on earth would you want to do this?” Well, contrary to popular belief, JavaScript isn't just “Java lite,” it's a powerful and useful language in its own right. However, because it's intended to execute inside a Web browser, it does have a few things missing from it. For example, in JavaScript, you have no way to play a sound or access a database. Java has both of these capabilities, and by making them available to JavaScript, we greatly enhance the types of tasks it can perform.