Please join us at the new JavaWorld Q&A Forums. Your existing login will work there. The discussions here are now read-only.


JavaWorld Talkback >> 958484

Pages: 1
Kris Thompson
Unregistered




Some examples please
      #1502 - 09/08/03 10:46 AM

OK, so you don't want use to use getter and setters (in general) can you give us some examples of what should be done. I can't really think of any elegant ways around the ways I use getters and setters. Even if I did agree with you I don't see what my alternative is in many cases.



Post Extras: Print Post   Remind Me!   Notify Moderator  
Chris Lane
Unregistered




Re: Some examples please [Re: Kris Thompson]
      #1521 - 09/08/03 05:09 PM

Actually, I have done this. For example I have a CustomerViewer class that runs on the client VM. It essentially uses a JFrame to display a list of customers (in a JTable). The CustomerViewer class calls a remote CustomerManager object (singleton) that queries a database and then instantiates a number of Customer objects: (e.g. new Customer(ResultSet rs)) The Customer reads the values from the result set into its fields - it know what they are - not CustomerManager. The fields are objects of Field class that actually wrap the underlying data type (e.g. string, number etc.). The CustomerManager returns a remote iterator to the CustomerViewer which reads several objects for display. The Customer objects are serialized to the client VM. The CustomerViewer sets up a JTable and custom table model that will display the Customers. The columns, labels and widths are set up by having the custom table model query the static methods of Customer passing a list of attributes. The attributes know their names, widths and titles - the column metadata. The customer knows its attributes. Therefore the table is drawn based on generic attribute information that comes from Customer. Finally, the custom table model fills in the rows of the JTable by asking for a each customer object for a string representation of each field associated with the each attribute that the customer has.

The net result is that the GUI code stays on the client and only abstractions such as Attributes (name, width, ...) and Fields (that have a string representation) are used between the GUI classes and "entity" class Customer.

It actually works! If a change occurs such as a column width, column name, the number of or order of displayed columns then only the Customer class must change.

A similar framework can be used for data entry on forms.

Want more?


Post Extras: Print Post   Remind Me!   Notify Moderator  
Anonymous
Unregistered




Re: Some examples please [Re: Chris Lane]
      #1525 - 09/08/03 05:49 PM

Chris,

This approach has advantages, but really only works for UI scenarios where a string-ified version of a property is sufficient for your operations. How, for example, would you create a form that *sets* the values of a customer. At some point, the data type needs to be known. If you are just passing arounds strings and converting them within the objects that doesn't really solve the problem, since the format of the string could presumably change (int to float for example).


Post Extras: Print Post   Remind Me!   Notify Moderator  
Barry Jenkins
Unregistered




Re: Some examples please [Re: Anonymous]
      #1526 - 09/08/03 06:41 PM

I worked with Chris on this particular framework, and the answer is still, "No, the datatype does not need to be known outside of the Customer object".

The Customer object knows about its Attributes (static information) and Fields (dynamic values). The Customer has a static method that creates a Form (our subclass of JPanel) and paints the various widgets on it. When a particular Customer object is viewed (i.e., the associate row in the JTable has been selected), an EntryForm is created, getting the Form from the Customer, and asks the Customer object attach itself to that Form. The attach method associates the Field values with the widgets on the Form, whatever they may be. The internal representations of the Customer are not exposed to the EntryForm or the CustomerManager.

We've refined the getForm method further, and can produce Forms that are appropriate to views (e.g., LONG, SHORT), or display Attributes according to specific business logic (e.g., is this field valid/displayable for this date, or in relation to the other values entered on the Form?). The Customer class can also check the values stored in its Fields to determine whether the Customer object is valid and can be written to the database.

Chris' reference to a string representation of the field
was inaccurate. It's actually a displayable representation. For example, the Form could display a date on a CustomDateField, as a formatted String, but holding a java.util.Date in its associated Field. Presumably, we could display an image in a Component and store it as a blob (if, say, we kept an ID photo of our Customer).

And it does work. I've used this framework on four separate applications, without much difficulty. And it's quite maintainable.

bsj


Post Extras: Print Post   Remind Me!   Notify Moderator  
Anonymous
Unregistered




Re: Chris and Barry [Re: Barry Jenkins]
      #1556 - 09/09/03 02:34 AM

How about giving some snippets of the code Iif propeitary then simplify to the crux) and not just talk about what calls what and what is not passed. Seeing is believing.

Post Extras: Print Post   Remind Me!   Notify Moderator  
Barry Jenkins
Unregistered




Example code: [Re: Anonymous]
      #1612 - 09/09/03 02:52 PM

Our BusinessObjects have a list of static information called Attributes, which have the constructor:
Code:
Attribute.java:
Attribute(Class parentClass, Class modelClass, String dbColumn, int maxLength, Object initialValue, boolean allowNull, String name, int length, Class theWidgetClass)


Where parentClass is the specific BusinessObject subclass (Customer), the modelClass is the underlying datatype (BigDecimal, String); maxLength, allowNull, dbColumn refer to the static information used to read data out of the database (we use Oracle databases almost exclusively, so scattering some SQL code in our framework seemed like a reasonable compromise); name, length, and theWidgetClass refer to how the Attribute is displayed.

Dynamic information is abstracted as FieldModels.
Code:
FieldModel.java:
FieldModel(Attribute attr)


Further, created an abstraction for our GUI elements called FieldComponent and extended some Swing Components (and custom created a few more), so that user-entered data is written directly to the component's FieldModel.
Code:
FieldComponent.java:
FieldComponent.setFieldModel(FieldModel model)


The principle is similar to a JTextComponent writing its data directly to a Document and then reading the Document's information. FieldComponents also have a sync() method that is called in response to user events (FocusEvents, MouseEvents,etc) to keep the FieldModel synchronized with the FieldComponent.

The BusinessObject's method for painting its Form (a subclass of JPanel) is quite simple:

Code:
Customer.java:
public static Form getForm() {
Form entryForm= new Form();
entryForm.add(attribute1.labelComponent());
entryForm.add(attribute1.fieldComponent());
//...
return form;
}




This creates a blank form. We ask the Customer to attach itself to this Form, populating the the FieldModels with the appropriate data:

Code:
Customer.java:
// NOTE: CustomNumericField is a subclass of JTextField (and implements FieldComponent) accepting only numeric characters
public static final Attribute attribute1 =
new Attribute(Customer.class, java.math.BigDecimal.class, "COLUMN_1", 8, new java.math.BigDecimal(0.0), false, "Column 1", 15, CustomNumericField.class);

// FieldModelList is a Vector of FieldModels.
private FieldModelList fields = new FieldModelList();
private FieldModel fieldModel1 = fields.addFieldModel( attribute1.createFieldModel(), this );
//...
public void attach(Form form, boolean editable) {
Enumeration enum = fields.fields();
while ( enum.hasMoreElements() )
form.attach( (FieldModel) enum.nextElement(), editable );
}
}

Form.java:
public void attach( FieldModel fm, boolean editable ){
//get the component from a Hashtable, indexed by name
Component comp = componentForName( fm.attribute().name() );
if (comp != null && comp instanceof FieldComponent){
fm.attach ((FieldComponent)comp, editable);
}
}

FieldModel.java:
public void attach( FieldComponent fui, boolean editable ) {
fui.setFieldModel( this );
fui.setUIEditable( editable );
fieldComponent = fui;
}



When the user wants to edit the Customer information, the CustomerEntryForm calls opens a transaction (tx), and passes the call to the CustomerManager:

Code:
CustomerManager.java:
// throws our custom BusinessObject exceptions
public static Customer editableCustomer(Customer customer, TX tx ) throws BOException.CannotGetLock, BOException.BODoesNotExist {
if ( tx == null || customer == null)
throw new NullPointerException("Tx and Customer required" );
try {
// BOFactory is a singleton which acts as a Facade to the Oracle database, hiding JDBC calls
return (Customer) BOFactory.getFactory().findLockedBO( Customer.class, customer.getBOPK(), null, tx );
} catch (RemoteException re) {
throw new RuntimeException("RemoteException Occurred: " + re);
}
}


editableCustomer is then attached to the Form. When the user wants to save this information, the CustomerManager is invoked again, passing the same transaction used to created the editableCustomer:

Code:
CustomerManager.java:
public static boolean saveCustomer( Customer customer, TX tx ) {
if ( tx == null || customer == null)
throw new NullPointerException("Tx and Customer required");
IBOFactory bof=null;
try {
BOPK key = BOFactory.getFactory().save( tx, customer );
customer.setBOPK( key ); // set key on local VM to that which was saved
return true;
} catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}

BOFactory.java:
public BOPK save( TX tx, BusinessObject bo ) throws RemoteException {
if (!isTXValid( tx )){
throw new BOException.General( "Invalid transaction" );
}
try {
bo.save( tx );
return bo.getBOPK();
} catch (SQLException e){
rollback( tx );
throw new BOException.SQLException( "SQL Exception saving bo to database tx= " + tx + " bo= " + bo, e );
} catch ( Throwable e ) {
e.printStackTrace();
throw new BOException.General( "Exception in BOFactory.save(tx,bo)", e );
}
}

Customer.java:
// combining BusinessObject.save and Customer.save for simplicity (shows update only):
public void save(TX tx) {
IBOFactory bof = BOFactory.getFactory();
ResultSet rset = null;
try {
String where = pkname + " = "+ bo.getBOPK();
rset = bof.runUpdatableQuery( tx, "Select " + SQLColumns+" from " + SQLTableName + " where " + where );
rset.next();
save( rset );
rset.updateRow();
} catch (RemoteException re) {
throw new BOException.General("RemoteException: " + re);
} catch (SQLException e) {
throw e;
} finally {
if (rset != null ) {
rset.getStatement().close();
rset.close();
}
}
}

public void save( ResultSet rset ) throws SQLException {
rset.updateInt( pkname, key.toInt() );
save( fields, rset );
}

public static void save( FieldModelList fields, ResultSet rset ) throws SQLException {
Enumeration e = fields.fields();
while (e.hasMoreElements()) {
FieldModel field = (FieldModel) e.nextElement();
field.save( rset );
}
}

FieldModel.java:
public void save( ResultSet rset ) throws SQLException {
//resets the attribute for this FieldModel
attribute();
// get the modelClass, dbColumn name from the Attribute class
if ( attribute == null || attribute.modelClass() == null )
return;
String dbColumn = attribute.dbColumn();
if ( dbColumn == null ||
dbColumn.equals(""))
return;
Class modelClass = attribute.modelClass();
try {
// write data from FieldModel to ResultSet
if (getValue()==null)
rset.updateObject( dbColumn, null );
else if (java.util.Date.class.isAssignableFrom( modelClass ))
rset.updateObject( dbColumn, new java.sql.Date(((java.util.Date)getValue()).getTime() ));
else if ( Boolean.class.isAssignableFrom( modelClass ) )
rset.updateBoolean( dbColumn, ((Boolean) getValue()).booleanValue() );
else if ( Integer.class.isAssignableFrom( modelClass ) )
rset.updateInt( dbColumn, ((Integer) getValue()).intValue() );
else
rset.updateObject( dbColumn, getValue() );
} catch( SQLException e ) {
e.setNextException( new SQLException("Error occurred in FieldModel.save() storing data to column= "+dbColumn) );
throw e;
}
}




All the code for user and database interactions are actually hidden in the FieldModel, Attribute, and FieldComponent classes. BusinessObject contains the basic behaviour for creating, reading, updating, saving and deleting BusinessObjects. All that the developer has to do is create the code specific for Customer's behaviour and its appearance on the Form. Displaying the Customer on a JTable is similar to displaying it on a Form. We use a BOTableModel (subclassing AbstractTableModel) to gather information about the Customer's known Attributes, necessary to create the JTable's structure (number of columns, headers, cell renderers, widths), and display the data from the FieldModel (I'll leave this as a Simple Exercise For The Student).

bsj


Post Extras: Print Post   Remind Me!   Notify Moderator  
Anonymous
Unregistered




Re: Example code: - not what Holub is on about [Re: Barry Jenkins]
      #1652 - 09/09/03 09:33 PM

I am not criticising your code - I think it is quite neat and have done similar stuff myself, however, I don't think Holub would like it. You are still basically shifting data around which he argues is a problem for maintainability. What I think Holub is trying to argue for is putting more intelligence inside objects but in this case there isn't really any intelligence required as it is simply get some data from a database and display it or store some data in a database. Data validation is an interesting area that you haven't addressed as is security. For example I have worked on systems where the validation rules for say a customer entry form vary depending on the office the user is based in and similarly different classes of users should only see certain fields - can your framework deal with requirements like that?

Post Extras: Print Post   Remind Me!   Notify Moderator  
Chris Lane
Unregistered




Re: Example code: - not what Holub is on about [Re: Anonymous]
      #1701 - 09/10/03 01:43 PM

>I am not criticising your code - I think it is quite neat >and have done similar stuff myself, however, I don't think >Holub would like it.

I cannot speak for Holub as to where he would like it or not but it based on articles and code he was written. I have also taken training from Holub - he may not want to admit this! It is based on the observer pattern which is built into Swing already with the document interface. When a business object's field model changes the GUI is automatically updated. Likewise with the GUI element. The cool thing is the information flows back to the attached business object without get/set methods, in fact more than one business object can be mapped to a single gui.


> You are still basically shifting data around which he >argues is a problem for maintainability. What I think >Holub is trying to argue for is putting more intelligence >inside objects but in this case there isn't really any >intelligence required as it is simply get some data from a >database and display it or store some data in a database.
>Data validation is an interesting area that you haven't >addressed as is security. For example I have worked on >systems where the validation rules for say a customer >entry form vary depending on the office the user is based >in and similarly different classes of users should only >see certain fields - can your framework deal with >requirements

In this simple example yes. The data has to move between the GUI and the represented business object. There is minimal coupling between the UI class and the business object because the UI components came from the business object. More complex logic like edit checks can be added to the business object. We did this again with the observer pattern. The FieldComponents (UI component) not only send its data directly to the FieldModel inside the Business Object but they also notify the Business Object that a change has occured. The BO can then do the edit checks, change field models (which automatically update the GUI), etc. We have implemented complete systems with this architecture. Unfortunately, we cannot give you all the code.

The *REAL* test is: if I want to change a field length, type, GUI element or edit check I change it in only one place.

I can also compose my UI from multiple BOs. The field values (object state) is always represented by the original objects. I can reuse classes such as Address or SSN that contain the edit checks, formating etc. With proper use of of layout managers I could change an address class (UI, representation internally etc.) and the change would ripple throughout the system.




Post Extras: Print Post   Remind Me!   Notify Moderator  
Anonymous
Unregistered




Re: Example code: [Re: Barry Jenkins]
      #28835 - 03/02/06 12:33 PM

Quote:

Our BusinessObjects have a list of static information called Attributes, which have the constructor:
Code:
Attribute.java:
Attribute(Class parentClass, Class modelClass, String dbColumn, int maxLength, Object initialValue, boolean allowNull, String name, int length, Class theWidgetClass)


Where parentClass is the specific BusinessObject subclass (Customer), the modelClass is the underlying datatype (BigDecimal, String); maxLength, allowNull, dbColumn refer to the static information used to read data out of the database (we use Oracle databases almost exclusively, so scattering some SQL code in our framework seemed like a reasonable compromise); name, length, and theWidgetClass refer to how the Attribute is displayed.

Dynamic information is abstracted as FieldModels.
Code:
FieldModel.java:
FieldModel(Attribute attr)


Further, created an abstraction for our GUI elements called FieldComponent and extended some Swing Components (and custom created a few more), so that user-entered data is written directly to the component's FieldModel.
Code:
FieldComponent.java:
FieldComponent.setFieldModel(FieldModel model)


The principle is similar to a JTextComponent writing its data directly to a Document and then reading the Document's information. FieldComponents also have a sync() method that is called in response to user events (FocusEvents, MouseEvents,etc) to keep the FieldModel synchronized with the FieldComponent.

The BusinessObject's method for painting its Form (a subclass of JPanel) is quite simple:

Code:
Customer.java:
public static Form getForm() {
Form entryForm= new Form();
entryForm.add(attribute1.labelComponent());
entryForm.add(attribute1.fieldComponent());
//...
return form;
}




This creates a blank form. We ask the Customer to attach itself to this Form, populating the the FieldModels with the appropriate data:

Code:
Customer.java:
// NOTE: CustomNumericField is a subclass of JTextField (and implements FieldComponent) accepting only numeric characters
public static final Attribute attribute1 =
new Attribute(Customer.class, java.math.BigDecimal.class, "COLUMN_1", 8, new java.math.BigDecimal(0.0), false, "Column 1", 15, CustomNumericField.class);

// FieldModelList is a Vector of FieldModels.
private FieldModelList fields = new FieldModelList();
private FieldModel fieldModel1 = fields.addFieldModel( attribute1.createFieldModel(), this );
//...
public void attach(Form form, boolean editable) {
Enumeration enum = fields.fields();
while ( enum.hasMoreElements() )
form.attach( (FieldModel) enum.nextElement(), editable );
}
}

Form.java:
public void attach( FieldModel fm, boolean editable ){
//get the component from a Hashtable, indexed by name
Component comp = componentForName( fm.attribute().name() );
if (comp != null && comp instanceof FieldComponent){
fm.attach ((FieldComponent)comp, editable);
}
}

FieldModel.java:
public void attach( FieldComponent fui, boolean editable ) {
fui.setFieldModel( this );
fui.setUIEditable( editable );
fieldComponent = fui;
}



When the user wants to edit the Customer information, the CustomerEntryForm calls opens a transaction (tx), and passes the call to the CustomerManager:

Code:
CustomerManager.java:
// throws our custom BusinessObject exceptions
public static Customer editableCustomer(Customer customer, TX tx ) throws BOException.CannotGetLock, BOException.BODoesNotExist {
if ( tx == null || customer == null)
throw new NullPointerException("Tx and Customer required" );
try {
// BOFactory is a singleton which acts as a Facade to the Oracle database, hiding JDBC calls
return (Customer) BOFactory.getFactory().findLockedBO( Customer.class, customer.getBOPK(), null, tx );
} catch (RemoteException re) {
throw new RuntimeException("RemoteException Occurred: " + re);
}
}


editableCustomer is then attached to the Form. When the user wants to save this information, the CustomerManager is invoked again, passing the same transaction used to created the editableCustomer:

Code:
CustomerManager.java:
public static boolean saveCustomer( Customer customer, TX tx ) {
if ( tx == null || customer == null)
throw new NullPointerException("Tx and Customer required");
IBOFactory bof=null;
try {
BOPK key = BOFactory.getFactory().save( tx, customer );
customer.setBOPK( key ); // set key on local VM to that which was saved
return true;
} catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}

BOFactory.java:
public BOPK save( TX tx, BusinessObject bo ) throws RemoteException {
if (!isTXValid( tx )){
throw new BOException.General( "Invalid transaction" );
}
try {
bo.save( tx );
return bo.getBOPK();
} catch (SQLException e){
rollback( tx );
throw new BOException.SQLException( "SQL Exception saving bo to database tx= " + tx + " bo= " + bo, e );
} catch ( Throwable e ) {
e.printStackTrace();
throw new BOException.General( "Exception in BOFactory.save(tx,bo)", e );
}
}

Customer.java:
// combining BusinessObject.save and Customer.save for simplicity (shows update only):
public void save(TX tx) {
IBOFactory bof = BOFactory.getFactory();
ResultSet rset = null;
try {
String where = pkname + " = "+ bo.getBOPK();
rset = bof.runUpdatableQuery( tx, "Select " + SQLColumns+" from " + SQLTableName + " where " + where );
rset.next();
save( rset );
rset.updateRow();
} catch (RemoteException re) {
throw new BOException.General("RemoteException: " + re);
} catch (SQLException e) {
throw e;
} finally {
if (rset != null ) {
rset.getStatement().close();
rset.close();
}
}
}

public void save( ResultSet rset ) throws SQLException {
rset.updateInt( pkname, key.toInt() );
save( fields, rset );
}

public static void save( FieldModelList fields, ResultSet rset ) throws SQLException {
Enumeration e = fields.fields();
while (e.hasMoreElements()) {
FieldModel field = (FieldModel) e.nextElement();
field.save( rset );
}
}

FieldModel.java:
public void save( ResultSet rset ) throws SQLException {
//resets the attribute for this FieldModel
attribute();
// get the modelClass, dbColumn name from the Attribute class
if ( attribute == null || attribute.modelClass() == null )
return;
String dbColumn = attribute.dbColumn();
if ( dbColumn == null ||
dbColumn.equals(""))
return;
Class modelClass = attribute.modelClass();
try {
// write data from FieldModel to ResultSet
if (getValue()==null)
rset.updateObject( dbColumn, null );
else if (java.util.Date.class.isAssignableFrom( modelClass ))
rset.updateObject( dbColumn, new java.sql.Date(((java.util.Date)getValue()).getTime() ));
else if ( Boolean.class.isAssignableFrom( modelClass ) )
rset.updateBoolean( dbColumn, ((Boolean) getValue()).booleanValue() );
else if ( Integer.class.isAssignableFrom( modelClass ) )
rset.updateInt( dbColumn, ((Integer) getValue()).intValue() );
else
rset.updateObject( dbColumn, getValue() );
} catch( SQLException e ) {
e.setNextException( new SQLException("Error occurred in FieldModel.save() storing data to column= "+dbColumn) );
throw e;
}
}




All the code for user and database interactions are actually hidden in the FieldModel, Attribute, and FieldComponent classes. BusinessObject contains the basic behaviour for creating, reading, updating, saving and deleting BusinessObjects. All that the developer has to do is create the code specific for Customer's behaviour and its appearance on the Form. Displaying the Customer on a JTable is similar to displaying it on a Form. We use a BOTableModel (subclassing AbstractTableModel) to gather information about the Customer's known Attributes, necessary to create the JTable's structure (number of columns, headers, cell renderers, widths), and display the data from the FieldModel (I'll leave this as a Simple Exercise For The Student).

bsj




Post Extras: Print Post   Remind Me!   Notify Moderator  
Pages: 1



Extra information
0 registered and 1 anonymous users are browsing this forum.

Moderator:   

Print Topic

Forum Permissions
      You cannot start new topics
      You cannot reply to topics
      HTML is disabled
      UBBCode is enabled

Rating:
Topic views: 11428

Rate this topic

Jump to

Contact us JavaWorld

Powered by UBB.threads™ 6.5.5