Compellingly Beautiful Software
Products Programming Contact
This branch of brising.com's website is similar to a set of articles I wrote for Netscape in 1997. Those articles are linked from several places in the web. They are still useful for people who are getting started in Java. The material here covers similar topics, but we are able to update these pages to give you more current information.
This article assumes you are building a standalone Java application that requires a GUI (Graphical User Interface). There are several things you need to understand before you can build this program in a practical way. Knowing the syntax and semantics of Java is just the beginning. As you read this article, keep in mind that you might have to read it more than once before you understand everything in it.
One of the most important design goals in good object programs is to separate the objects that hold the data from the objects that display the data. This allows you to redesign your user interface without changing the internal workings of your program.
Of course, these two sets of objects have to communicate somehow. Here are some terms and rules that will help:
When we say that models only know implicitly about views, we mean that model code never refers to any features of its views. However, it may, in a very general way, broadcast information about state changes to whichever objects might care about them. These objects-that-care are usually views.
When we say that views may know explicitly about models, we mean that view code exists only to serve its model, so it is OK for a view to refer to the features of its model. Views usually hold an explicit reference to their models.
The dependency relationship between models and views are maintained with the help of some very basic library code. There are four class/interface pairs that you need to know about:
Each of these class interface pairs do essentially the same thing, but in slightly different ways. You'll choose one of these pairs. The first member of each of these pairs is a base class for your model. The second member of each of these pairs is implemented by your view. Your code will be very similar in structure, whichever you choose.
Be sure to take the separation of models and views very seriously. Failure to do so will have unpleasant consequences as your code ages, which it will start to do sometime around the middle of next week.
As you build object-oriented programs, you'll often write expressions that are essentially asking "Are you the object that I hope you are?" There are two ways of trying to answer this question. The first way is to see if two objects look the same. We do this with the equals() message, which returns either true or false. The second way is to see if the two objects are in fact the same object. We do this with the == operator, which also returns either true or false.
In general, == is much more efficient than equals(), so we try to use == whenever we can. But one situation where this becomes difficult is when we are asking, not about a thing itself, but about some thing's name. The == operator only uses one machine instruction to execute, but comparing strings takes several instructions per character, and names are often quite long.
Because string comparison is so slow, programmers often use numbers as identifiers instead. This might be more efficient, but it's hard for a human reader to interpret these numbers during a debugging run. Also, the allocation of unique numeric identifiers can be very difficult in many applications. For those cases where a readable name is preferable to a numeric identifier, a simple programming trick can bring back most or all of the efficiency that would be lost by a naive use of string-based identifiers. This trick is called string internment.
There is a straightforward description of string internment, but differences in various Java compilers make it hard to demonstrate this simple definition with assurance. Keep this in mind if you try to validate this idea using simple code: your compiler can and probably will outsmart you in the simple case, but not in a more complicated example. Use your head for a moment, and not your CPU, and you'll understand. Let's look at three code fragments:
The first expression returns true: these two strings look the same. The second expression returns false: these two strings look the same but are different objects. The third expression returns true: these two strings are the same object. That's because intern() looks in a special collection of strings that have already been interned. If it does not find a string in that collection that looks the same as the string on which intern() was invoked, it adds the string to the collection and returns that string. If it does finds a string in that collection that looks the same as the string on which intern() was invoked, it returns the pre-existing string.
One would not really write an expression like the third one, above. Internment is a fairly expensive operation, so we usually try to do it early in a program's execution, and then pass around the strings that are already interned. When used properly, interned strings can save your program a lot of time.
Incidentally, it's the second expression, above, in which modern compilers are most likely to fool you by returning true for this simple example. In the most general case, this kind of expression will return false, and it is the general case that your code must handle correctly.
Here is an example of a domain model. This is a classic example, and typical of classes of the kind that you will write often. You are welcome to use this code, and all Java source code found on this webpage, in your own works.
At brising.com, we use a structured naming technique in our code. You might want to read about it first.
We'll build this domain model in two stages. That's because the JavaBeans® base classes aren't as convenient as we might like. The code shown here makes the Beans functionality a little friendlier. brising.com's base classes are inherently easier to use, and are as much as 30 times more efficient, too.
import java.beans.*; public class Model extends Object { // INSTANCE VARIABLES PropertyChangeSupport hoSupport; // CONSTRUCTORS public Model() {hoSupport = new PropertyChangeSupport(this);} // PUBLIC INSTANCE METHODS public void addPropertyChangeListener (PropertyChangeListener aoListener) {hoSupport.addPropertyChangeListener(aoListener);} public void removePropertyChangeListener(PropertyChangeListener aoListener) {hoSupport.removePropertyChangeListener(aoListener);} // PROTECTED INSTANCE METHODS protected void change(String asProperty,Object aoValueOld,Object aoValueNew) {hoSupport.firePropertyChange(asProperty,aoValueOld,aoValueNew);} }
Now we'll build the second stage of our model. This is the part that our application really wants to have. This class describes a Person as having three attributes: name, address, and phone. We will use Model as the base class.
public class Person extends Model { // STATIC VARIABLES static final public String gksUpdateAddress = "updateAddress".intern(); static final public String gksUpdateName = "updateName" .intern(); static final public String gksUpdatePhone = "updatePhone" .intern(); // INSTANCE VARIABLES Object hoAddress; Object hoName; Object hoPhone; // CONSTRUCTORS public Person() {this("","","");} public Person(Object aoName,Object aoAddress,Object aoPhone) { super(); hoAddress = aoAddress; hoName = aoName; hoPhone = aoPhone; } // PUBLIC INSTANCE METHODS public void clear () { setAddress(""); setName (""); setPhone (""); } public Object getAddress() {return hoAddress;} public Object getName () {return hoName ;} public Object getPhone () {return hoPhone ;} public void setAddress(Object aoAddress) {change(gksUpdateAddress,hoAddress,hoAddress = aoAddress);} public void setName (Object aoName ) {change(gksUpdateName ,hoName ,hoName = aoName );} public void setPhone (Object aoPhone ) {change(gksUpdatePhone ,hoPhone ,hoPhone = aoPhone );} public String toString () {return getName().toString();} }
Note the types of Person's instance variables. When defining variables, choose the most general type that is reasonable to use. This makes your code more flexible and easier to maintain.
Note the get-methods and set-methods. This get/set convention is common in object programming, and some software tools and frameworks, such as brising.com's Slamdunk, require you to have get-methods and set-methods. The arguments to change() denote the property that is changing, the old value of that property, and the new value of that property. Note the use of interned strings here; they will be important later.
Note the clear() method. Just assigning null or "" to a Person's instance variables isn't good enough. It is important to use the set-methods.
Note the toString() method. It appears to be more complicated than it needs to be. It isn't!
There are other methods we might want to implement here, most notably equals() and hashCode(), but these are more advanced topics.
This user interface has three textfields and a button. One textfield displays a Person's name. One textfield displays a Person's address. One textfield displays a Person's phone. The button clears all three of these attributes, not just in the display, but in the Person object itself.
The picture that you see here is just that: a picture. It is not a live applet.
In this example, we'll use the so-called Swing user interface components. You can find documentation for these in the javax.swing package.
We will begin by subclassing javax.swing.JPanel, and then deal with the following concerns: component initialization, component response, model acquisition, and model response. There are other reasonable ways of coding parts of this, some of which are considered more "modern", for example the use of ActionListeners. These other techniques do not reduce the amount of code for this example, and in fact make it larger, although they can be helpful when you are building very complex UIs.
import java.awt.*; import java.awt.event.*; import java.beans.*; import javax.swing.*; import javax.swing.border.*; public class PersonPanel extends JPanel implements ActionListener,FocusListener,PropertyChangeListener { // INSTANCE VARIABLES JButton hoButtonClear; Person hoPerson; JTextField hoTextFieldAddress; JTextField hoTextFieldName; JTextField hoTextFieldPhone; // CONSTRUCTORS /* This constructor initializes all of my subordinate components. */ public PersonPanel() { super(); hoButtonClear = new JButton ("clear"); hoTextFieldAddress = new JTextField(); hoTextFieldName = new JTextField(); hoTextFieldPhone = new JTextField(); hoButtonClear .addActionListener(this); hoTextFieldAddress.addActionListener(this); hoTextFieldName .addActionListener(this); hoTextFieldPhone .addActionListener(this); hoTextFieldAddress.addFocusListener(this); hoTextFieldName .addFocusListener(this); hoTextFieldPhone .addFocusListener(this); hoTextFieldAddress.setBorder(new TitledBorder("Address")); hoTextFieldName .setBorder(new TitledBorder("Name" )); hoTextFieldPhone .setBorder(new TitledBorder("Phone" )); hoButtonClear .setToolTipText("clear" ); hoTextFieldAddress.setToolTipText("Address"); hoTextFieldName .setToolTipText("Name" ); hoTextFieldPhone .setToolTipText("Phone" ); setLayout(new java.awt.GridLayout(4,1)); add(hoTextFieldName ); add(hoTextFieldAddress); add(hoTextFieldPhone ); add(hoButtonClear ); enableAll(false); } // PUBLIC INSTANCE METHODS /* These three methods respond to events from my subordinate components. */ public void actionPerformed(ActionEvent ae) { final Object koSource = ae.getSource(); if (false){} // do nothing, but simplify maintenance editing else if (koSource == hoButtonClear) hoPerson.clear(); else if (koSource == hoTextFieldAddress) hoPerson.setAddress(hoTextFieldAddress.getText()); else if (koSource == hoTextFieldName) hoPerson.setName(hoTextFieldName .getText()); else if (koSource == hoTextFieldPhone) hoPerson.setPhone(hoTextFieldPhone .getText()); else throw new RuntimeException ("ActionEvent from unknown source: "+koSource); } public void focusGained(FocusEvent ae) {} // do nothing public void focusLost (FocusEvent ae) { final Object koSource = ae.getSource(); if (false){} // do nothing, but simplify maintenance editing else if (koSource == hoTextFieldAddress) hoPerson.setAddress(hoTextFieldAddress.getText()); else if (koSource == hoTextFieldName) hoPerson.setName(hoTextFieldName .getText()); else if (koSource == hoTextFieldPhone) hoPerson.setPhone(hoTextFieldPhone .getText()); else throw new RuntimeException ("FocusEvent from unknown source: "+koSource); } /* These two methods deal with model acquisition. */ public Person getPerson() {return hoPerson;} public void setPerson(Person aoPerson) { if (null != hoPerson) hoPerson.removePropertyChangeListener(this); if (null != aoPerson) { hoPerson = aoPerson; hoPerson.addPropertyChangeListener(this); hoTextFieldAddress.setText(hoPerson.getAddress().toString()); hoTextFieldName .setText(hoPerson.getName ().toString()); hoTextFieldPhone .setText(hoPerson.getPhone ().toString()); enableAll(true); } else { hoPerson = null; hoTextFieldAddress.setText(""); hoTextFieldName .setText(""); hoTextFieldPhone .setText(""); enableAll(false); } } /* This method responds to changes in my model. */ public void propertyChange(PropertyChangeEvent ae) { final String ksUpdate = ae.getPropertyName(); final Object koValue = ae.getNewValue(); final String ksValue = null != koValue ? koValue.toString() : ""; if (false){} // do nothing, but simplify maintenance editing else if (ksUpdate == Person.gksUpdateAddress) hoTextFieldAddress.setText(ksValue); else if (ksUpdate == Person.gksUpdateName ) hoTextFieldName .setText(ksValue); else if (ksUpdate == Person.gksUpdatePhone ) hoTextFieldPhone .setText(ksValue); else throw new RuntimeException ("Unknown property for "+getClass().getName()+": "+ksUpdate); } // PROTECTED INSTANCE METHODS /* This is a utility method. */ protected void enableAll(boolean ay) { hoButtonClear .setEnabled(ay); hoTextFieldAddress.setEnabled(ay); hoTextFieldName .setEnabled(ay); hoTextFieldPhone .setEnabled(ay); } }
Disregarding comments and blank lines, that's about 120 lines of code.
(cp (lg 4 1)(bi "Name" (cf))(bi "Address" (cf))(bi "Phone" (cf))(cb null "clear"))
© 2004 brising.com