|
|
DESIGN oo features | java language | design patterns | uml | multithreading | trends
Last updated: February 2005
Java is an object-oriented language,
meaning that its primary language constructs such as
classes and interfaces as well as many other features
make it easy to implement a model created through the
process of OOD
(Object-Oriented Design), a process greatly aided
by the growing shared catalog of
patterns used by experienced developers, including those
who wrote the core Java API.
|
|
|
oo features
OOD (Object-Oriented Design) is a design process that models
systems in terms of objects. An object-oriented language encourages
OOD with constructs such as classes and interfaces (in the case
of Java). The main features of OOD are:
Abstraction — In order to model the system well, it is
necessary to decide upon a set of objects which are useful for
your purposes and appropriate to the level of detail you
wish to work at. This is abstraction.
Encapsulation — the bundling together of state and
process information in classes (or capsules) in such a way
that they hide their implementation details
from each other. Each provides only an interface that other
classes can use — a contract that it is committed to.
The advantage is that a developer is then free to change a
class's implementation without
repercussions in the rest of the system, as long as the contract
is maintained.
Inheritance — classification of objects in terms of
is-a relationships, for instance,
class Engineer extends Person.
Commonalities can be abstracted out and put in superclasses
(like Person) to build a hierarchy.
Polymorphism — the ability for the same operation
to be defined for different types of objects. In Java you
can have objects share a behavior by having their classes
implement a common interface. This interface is like an
abstract type whose methods can be called on objects without
knowing what concrete classes they belong to — until runtime
(late binding).
Composition/delegation — a freer alternative to
inheritance, which allows you to extend the behavior of a
class A by creating a class B which uses instances of class
A to provide A's functionality in addition to its own
extra functionality.
RESOURCES
A talk on object-oriented design (OOD).
|
|
|
java language One language construct which Java uses to support OO is the interface, borrowed from Objective C. An interface consists of a set of methods without bodies: thus, it specifies behavior without implementation. It is useful for establishing contracts between objects, which simplifies maintenance and promotes reusability. Interfaces are also important for supporting polymorphism (see above). Whenever code is running, there is often a main line of expected behavior, and a set of unusual conditions which may occur. Java models these unusual conditions as objects called exceptions - a very useful feature, peculiar to Java. If a method throws an exception, the compiler makes the calling code handle it — thus the programmer is reminded to take care of predictable errors. Moreover, the error-handling statements can be grouped and separated from the main line flow, rather than being spread throughout the code in spaghetti fashion, or in multiple layers of invocation, as can happen with the C++ return code model. Java has strong support for streams in I/O and networking. Streams abstractly model data flow in a system. For instance, to write to a file, you could construct a FileOutputStream, passing in a File object, and call the write methods of the FileOutputStream. To support internationalization, since JDK 1.1 Java has provided enhanced stream objects called Readers and Writers which deal with characters rather than bytes. Some programs will require parts of their state to be saved between invocations. For instance, suppose you have a simple Address Book application: when it is opened it must load all the information that was entered in previous sessions. How can you store this information? You might save it to a file in your own custom data format. But a simpler option might be to use Java's support for object persistence — this allows you to save the whole state of objects in files in a process called serialization and then recover it later. If you want to to interact with libraries and applications written in other languages use JNI (Java Naming Interface). A C program for instance could use the JNI to create the Java VM, start the Java application, attach to the VM, and inspect classes and methods in the Java application. The JDK 1.1 introduced a special delegation event model. In this model events are generated by sources and objects can become listeners by registering with the sources. An object can delegate event handling to one of its components, e.g. a Panel delegating mouse event handling to a button. To listen to a source an object must implement the listener interface, for example: public class MyListener implements ActionListener {... } This class must now provide the body of the method actionPerformed(ActionEvent e) specified in the ActionListener interface. (rather than the action method used in the JDK 1.0). There are different sets of listeners for different types of events: for instance the MouseListener interface can be used to respond to mouse-specific events like MouseEntered and MouseClicked. To save the developer the necessity of implementing all the methods of a given listener interface, the JDK 1.1 includes adapters for most listeners which give default empty bodies to each method — the developer can then simply extend the adapter and choose which methods he wishes to implement. Finally, a Java VM, to be compliant with the language specification, must operate in a certain way which guarantees essential security features. Before any class bytecode reaches the execution engine of a VM, it is thoroughly checked by the class loader currently operating in the VM. The class loader passes it through a verifier to check if it conforms to the Java language specification, resolves the class by loading all the classes it depends on, and consults with a security manager (or access controller since JDK 1.2) before letting the class access any system resources. By default the class loader is the primordial class loader, loaded when the VM starts up, but it can be replaced by a user-defined class loader for special purposes like loading untrusted classes over a network. RESOURCES |
|
|
design patterns After designing and implementing software systems for a few years, an engineer may notice patterns occurring and re-occurring — especially patterns of arranging objects in ways which are useful or overcome certain common problems. Other engineers, running into the same problems, may have developed similar solutions. These solutions are usually unspoken, but it makes sense to articulate them in a shared pattern language. The idea of having pattern languages had been developed in the field of architecture by Christopher Alexander in the 1970's. In 1995 four OO experts (the "Gang of Four" — Gamma, Helm, Johnson, and Vlissides) formalized a set of 23 patterns for software design in their book Design Patterns — since then, many more patterns have been suggested. A design pattern is specified not in code, but verbally: a pattern is described in terms of a problem situation and an OO technique for solving it. To be more specific, the description of a pattern will generally include such headings as Motivation, Applicability, Consequences, Implementation, Code Example, and Related Patterns. The Gang of Four used C++ for code samples, but patterns are also extremely useful when coding with Java, and are in fact to be found throughout the core APIs. To take an example, a familiar pattern is Proxy. There are certain situations where part of a software system wishes to give the illusion to the rest of the system that an object is present when in fact it is not. For instance, you wish to make requests of a desired object which is on a remote server; or, a graphical application, when loading a document with lots of images, may present a view of the document before all the objects in the document are loaded. In these cases, each missing object is replaced with a proxy - a sort of intelligent reference to the object which stands in for it and gives correct responses. In client-server protocols, it is very common to have a stub class which represents the remote server object in the client address space, thus allowing calls to be made transparently. RESOURCES An introduction to Christopher Alexander for software developers. Patterns for Java and Distributed Computing.
|
|
|
uml To gain an overview of how a software system works (or should work), it is often useful to draw a diagram; and to maximize the diagram's communicative value it is best to use a common graphical notation. After decades of experimenting with design methodologies, theorists (principally leaders Booch, Rumbaugh, and Jacobson) agreed upon a standard for representing object-oriented systems in the mid-nineties called UML (Universal Modelling Language). UML specifies a set of diagrams which enable you to capture everything about a software project: the intended uses of the system, what the code structure will look like, what the interactions will be at runtime, how the hardware will be arranged to support it all, and so on. More specifically, the nine type of diagrams are:
Many UML design tools are available, the most popular ones being Rational Rose, and Together Designer. Such systems allow you to create a design by point-and-click, and may also have the capacity to analyze existing code and create the diagram for you (reverse engineering). With Rational Rose, if you change the code in a supported IDE, the diagram will automatically change for you; conversely, additions to the diagram can result in code generation. Most good tools will allow you to export your diagram to XMI, the XML format for UML. Below is shown one of the smaller UML editors, the ArgoUML tool. It's open-source (though there is a product version called Poseidon), implemented in Java, but not sluggish. One interesting feature is that it makes suggestions for your design as you go along. Here it is guessing that I should use the Singleton design pattern for my main application class.
|
|
|
|
An introduction to UML from The Software Design Resource Center.
|
|
|
multithreading It is often useful to have a program do two or more things at the same time. For instance, while a process is stuck waiting for I/O (e.g. over a network), it should be able to do something useful like respond to user actions in the UI in the meantime. Traditionally this area of concurrent programming has been a specialized area: some computer languages require the programmer to explicitly set up complex systems of locks, mutexes, or semaphores; others may encourage certain techniques like polling or using callback methods for particular problems. Java simplifies concurrent programming greatly by having, within a single Java process, multiple paths of execution called threads which can be created and controlled by the application programmer. While the programmer has a high-level access to the threads, allowing him to start, stop, interrupt, group, and prioritize them, the actual details of scheduling are taken care of by Java: either the VM can take care of scheduling the threads itself (green threads) or for efficiency it can hand them off to an operating system like Solaris (native threads). Central to the threading model is Java's Thread class. In order to create a new thread, the programmer can either subclass Thread or, more flexibly, declare an application class to have the role Runnable and pass it as a target to a new instance of Thread. The functionality of the new thread is written in Thread's run() method. The programmer does not call run() directly; he calls start() which sets the thread up in the VM and then calls run(). There are also some good mechanisms for allowing threads to work with each other. For instance, if you want a thread to wait until another has finished doing something (e.g. loading pictures), you don't have the keep polling the second thread: just call the join() method — you are "joining" the first thread to the second. More advanced communication often relies on the wait() and notify() methods. Consider a situation where some threads (producers) are regularly making new data available, and others (consumers) are getting and using that data. How do the consumers know when new data is available? One way is to have a common data structure which both producers and consumers use. The consumer calls the wait() method on the data structure in a loop, and the producers call notify() on it to indicate that new data is available (or more likely notifyAll() to give more than one consumer a chance to wake up). For this data structure you could have just a Vector, or custom code a queue class, or a bounded buffer class to limit the number of items in it. This is all possible because the wait() and notify() methods are not of class Thread but of the general class Object. Even more importantly, unlike the case with separate processes, different threads can share objects just as different classes normally can. Although Java handles the low-level details of thread management, many of the usual problems of concurrent programming still apply. If two threads are reading and writing the same data at the same time without knowing about each other, then the result can be difficult to predict — there may occur something known as a race condition, a situation where the data becomes corrupted. To avoid this, Java provides the synchronized primitive. This allows the programmer to section off areas of code so that only one thread can execute it at a particular time for a particular object. However, if the synchronized primitive is used unwisely, execution may slow down considerably, or even grind to a halt if a condition called deadlock occurs — for example, where two threads are each waiting on each other to release respectively held resources (especially locks), and each cannot proceed until the other "gives in". RESOURCES |
|
|
trends Some claim that the next big step in programming methodologies -- after OOP -- is AOP. They argue that an OO system typically has many concerns that cannot be neatly encapsulated in their own objects but that crosscut all or much of the system. Examples of such concerns would be logging, security, and transaction handling, all of which should be separated from the core business logic. The EJB architecture is an example of a system that addresses several concerns, but it does so in a domain-specific way. AOP uses the more generic technique of weaving. Crosscutting concerns (called aspects) like logging and security can be woven into the functionality either at the source code level or at runtime by making the JVM AOP-aware. The important thing is to have a standard set of rules for combining aspects and core functionality. With implementations such as AspectJ (which merged with AspectWerkz in January 2005), you can define aspects using a class-like syntax ( public aspect AutoLog { ... } ) and inside them specify join points in your main body of code at which aspect-oriented code called advice should be executed. The latest version of JBoss relies heavily on aspects. Another important trend in Java is IoC (Inversion of Control), sometimes called by the more descriptive term Dependency Injection. This pattern addresses a common problem in large systems: modules/layers of functionality tend to get entangled. In IoC software like PicoContainer, Spring, and WebWork2, the dependencies between objects are managed by a container so the developer can configure them, and test modules independently of each other. In PicoContainer for instance a class specifies what it needs in its constructor, and those objects are constructed automatically when the class is needed, but no constructor needs to be explicitly called in the source code anywhere. The impact of Dependency Injection on good OO design is still being debated. In the J2EE world, whether heavyweight or lightweight, a great deal of configuration is generally needed, and developers are increasingly relying on code generation to simplify matters. For instance an IDE can allow a developer to generate EJB source code from configuration files. The oppposite may also be desirable. Consider a class that has Struts information specific to it and Hibernate information specific to it. It would be nice to put that information inside the source code for the class instead of having to go to scattered configuration files, so what many developers do is embed tags in comments and then use the tool XDoclet to look at their source code and write out the configuration files during each build. The introduction of annotations in J2SE 5.0 may bring further structure and popularity to this area. Annotations are aimed at helping tools to process attributes which can be in the source code or even carried forward into the runtime. RESOURCES
Introduction to AOP from JavaWorld
Introduction to IoC/Dependency Injection
|
|