Well-Designed Models


To begin with, the Action should be as insulated as possible from the actual implementation of the model. This can be done by using abstraction. For example, if the model uses JNDI to look up a username and password in an LDAP server, you shouldn't put JNDI-specific code in the Action .

Instead, the Action should communicate through an interface (or at least a well-defined API) to the model, and the model should do all the work of setting up and making the JNDI call. That means if you want to change the model later to have it use JDBC to talk to Oracle, for example, only the model code would change; the Action could remain untouched.

To illustrate this, take a look at Stock.java (see Listing 9.1), which implements the model for stocks through Torque.

Listing 9.1 Stock.java
 package stocktrack.torque; import org.apache.torque.om.Persistent; import java.util.Date; import java.util.GregorianCalendar; import stocktrack.torque.StockPriceHistory; import java.util.Random; import java.util.List; import org.apache.torque.util.Criteria; import org.apache.log4j.Category; /**  * The skeleton for this class was autogenerated by Torque on:  *  * [Wed Jul 03 23:37:59 EDT 2002]  *  * You should add additional methods to this class to meet the  * application requirements. This class will only be generated as  * long as it does not already exist in the output directory.  */ public class Stock     extends stocktrack.torque.BaseStock     implements Persistent {   static Category cat = Category.getInstance(Stock.class);   // Always make sure this is an actual date the   // market would be open (not Sat or Sun).   private Date firstRecordedDay =          new GregorianCalendar(2002,0,1,9,0,0).getTime();   public static Stock findStockBySymbol(String symbol) {     try {       Criteria c = new Criteria();       c.add(StockPeer.STOCK_SYMBOL, symbol);       List l = StockPeer.doSelect(c);       if ((l == null)  (l.size() == 0)) return null;       return (Stock)l.get(0);     } catch (Exception ex) {       cat.error("findStockBySymbol", ex);     }     return null;   }   public StockPriceHistory getLastQuote() throws Exception {     return StockPriceHistory.getLastQuoteForId(this.getStockId());   }   public StockPriceHistory getLastClose() throws Exception {     return StockPriceHistory.getLastCloseForId(this.getStockId());   }   public StockPriceHistory getLatestQuote() throws Exception{     Random r = new Random();       Date today = new Date();       StockPriceHistory sph = this.getLastQuote();       Date lastRecordedPrice = null;       double lastPrice = 0;       if (sph == null) {         cat.debug("No prior quote found");         lastRecordedPrice = firstRecordedDay;         lastPrice = (r.nextInt(20)+10) + r.nextFloat();         sph = new StockPriceHistory();         sph.setPrice(lastPrice);         sph.setPriceTimestamp(lastRecordedPrice.getTime());         sph.setStock(this);         sph.setPriceClose("N");         sph.save();       } else {         lastRecordedPrice = new Date(sph.getPriceTimestamp());         lastPrice = sph.getPrice();       }       GregorianCalendar current = new GregorianCalendar();       GregorianCalendar last = new GregorianCalendar();       last.setTime(lastRecordedPrice);       last.add(GregorianCalendar.HOUR, 1);       while (current.after(last)) {          //Markets close at 4 PM         if (last.get(GregorianCalendar.HOUR_OF_DAY) > 16) {             //Reset to 9AM the next morning             last.set(GregorianCalendar.HOUR_OF_DAY, 9);             //Move it forward 1 day             last.add(GregorianCalendar.DATE, 1);         }         if ((last.get(GregorianCalendar.DAY_OF_WEEK) ==             GregorianCalendar.SATURDAY)) {            //Move it forward to Monday            last.add(GregorianCalendar.DATE, 2);         }         if (!current.after(last)) break;         lastPrice = lastPrice + (r.nextFloat() - 0.50);         if (lastPrice < 10) lastPrice += 1;         if (lastPrice > 40) lastPrice -= 1;         sph = new StockPriceHistory();         sph.setStock(this);         sph.setNew(true);         sph.setPrice(lastPrice);         sph.setPriceTimestamp(last.getTime().getTime());        if (last.get(GregorianCalendar.HOUR_OF_DAY) == 16) {          sph.setPriceClose("Y");         } else {          sph.setPriceClose("N");         }        sph.save();        last.add(GregorianCalendar.HOUR, 1);        }       return sph;    } } 

There are two important things to note in this file. First, all the Torque-specific details of the model implementation have been coded here, leaving the Action pure. For example, if the Action needs access to a stock by looking up the symbol name , it calls Stock.findStockBySymbol rather than setting up a Torque Criteria and doing the SQL search in the Action . So if, at a later date, you were to switch from Torque to another object-modeling tool or replace this entirely with an EJB implementation, the only thing you'd have to do is make sure that you continued to respect the findStockBySymbol API.

The getLatestQuote code illustrates another important value of placing all your business logic in the model. At the moment, the stock quotes are being created out of thin air by a random number generator. Hopefully, this will not be the case in the finished application. However, by isolating this fact from the Action , it doesn't care where the quotes are coming from, only that they are available. So, at a later date, the correct code could be put in, and the rest of the application would continue unaware of the change.

If the symbol is not available at all or if an exception is thrown during the lookup, null is returned. There's an active debate in the Java community whether these types of errors should return null or throw an explicit exception that must be caught by the calling code. I tend to prefer to reserve throwing an exception for an actual error condition or a case in which returning null would be semantically meaningful.



Struts Kick Start
Struts Kick Start
ISBN: 0672324725
EAN: 2147483647
Year: 2002
Pages: 177

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net