浏览 2968 次
锁定老帖子 主题:设计模式在gwt中的应用
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-12-10
设计模式在gwt中的应用
本文主要介绍gwt中的模式,或模式的应用。结合近一段时间项目的开发,演示一些示例。部分代码可能不够完整,但尽量保证能够阅读清晰。 按四人组对模式的分类:创建型1(1.1SingleTon,1.2Builder,ProtoType,Abstrct Factory,Factory Method),结构型2(2.1Decorator,2.1Composite,Proxy,Facade,Adapter,2.2Bridge,Flyweight),行为型3(3.1Observer,3.2Command,Strategy,State,Template Method,Visitor,Mediator,Memento,Chain of Responsibility,Interpreter,Iterator) 顺序进行描写。不求一一覆盖,只为抛砖引玉,期望和大家一起交流
问题: 目前项目有一个带分页功能的电影浏览,用户在页面间切换频率很大。造成server traffic. 情景: 点上一页,下一页,都需要到服务器去取数据。 解决方案: 在客户端设置cache,第一次访问后,存储当前页数据。当存储满100页数据时,清掉最早存储的数据,以更新数据和防止memory out. /** * GWT of this version have no class LinkedHashMap,so class OrderedMap * implements much of LinkedHashMap's function. The class be used in cache * data. Attetion,the OrderedMap has only a global instance. * */ public class OrderedMap implements IsSerializable { private static ArrayList keys = new ArrayList(); private static HashMap map = new HashMap(); private static OrderedMap singleton=new OrderedMap(); private OrderedMap(){} public static OrderedMap getInstance(){ return singleton; } public ArrayList getKeys() { return keys; } public Object put(Object key, Object value) { if (keys.contains(key)) { keys.remove(key); } keys.add(key); return map.put(key, value); } public Object remove(Object key) { keys.remove(key); return map.remove(key); } public Object remove(int index) { Object o = keys.get(index); return remove(o); } public void clear(){ keys.clear(); map.clear(); } public Object get(Object key) { return map.get(key); } public boolean containsKey(Object key) { return map.containsKey(key); } public int size() { return map.size(); } } /* <p> * first visit the data, store it.Then second visit,using the cache data. * cached data when setMovieElement,using cache when click "cmdPreviousMovie","cmdNextMovie" * and fetch more page once to reduce server traffic. * attention,the first entry page per genre will not use cache forever! *</p> */ public class MovieGridWithCache extends MovieGrid { // use the symbol to decide to use cache or not protected class Symbol implements Cloneable { private String genreid; // movie grenre id private String currentpage; private String languageid; // moveie language id public String getCurrentpage() { return currentpage; } public void setCurrentpage(String currentpage) { this.currentpage = currentpage; } public String getGenreid() { return genreid; } public void setGenreid(String genreid) { this.genreid = genreid; } public String getLanguageid() { return languageid == null ? "" : languageid; } public void setLanguageid(String languageid) { this.languageid = languageid; } public Symbol(String genreid, String currentpage, String languageid) { this.genreid = genreid; this.currentpage = currentpage; this.languageid = languageid == null ? "" : languageid; } public Symbol() { } public Object clone() { Symbol o = new Symbol(); o.genreid = new String(this.genreid == null ? "" : this.genreid); o.languageid = new String(this.languageid == null ? "" : this.languageid); o.currentpage = new String(this.currentpage == null ? "" : this.currentpage); return o; } public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (o instanceof Symbol) { Symbol symbol = (Symbol) o; return (symbol.genreid.equals(this.genreid) && symbol.languageid.equals(this.languageid) && symbol.currentpage .equals(this.currentpage)); } else return false; } // define the hashCode because of using as a Map key,otherwish, the // map.get(key) get the null value. public int hashCode() { int hash = 1; hash = hash * 31 + genreid == null ? 0 : genreid.hashCode(); hash = hash * 29 + languageid == null ? 0 : languageid.hashCode(); hash = hash * 23 + currentpage == null ? 0 : currentpage.hashCode(); return hash; } } protected final static int PAGE_NUMBER=3; // fetch more page to reduce server traffic protected final static int maxCacheSize = 100; // that's to say: store 100 page data. // the map store pageId,genreid,languageid and _movieList protected static OrderedMap cache = OrderedMap.getInstance(); public MovieGridWithCache(ScreenData screenData, String genreid, String movielanguageid) { super(screenData, genreid, movielanguageid); } // it's a Callback,be used in class MovieDataAccess public void setMovieElement(JSONValue jsonMovies) { _movieList = MovieDataAccess.JSON2MovieDTO(jsonMovies); if (_currentPage ==0) showPage(); cachePage(); } public void showPage(int page) { super.showPage(page); } public void showPage() { showPage(0); } /** * Attention! As the first entry of page ,it will not use the cache!! * * @param genreid * @param movielanguageid: * if null or blank,fetch all movielanguage */ public void ShowPageByGenreWithCache(String genreid, String movielanguageid) { _currentPage = 0; this._genreID = genreid; this._movielanguageID = movielanguageid; _movieDataAccess = new MovieDataAccess(this, Integer.toString(_currentPage), Integer .toString(PAGE_SIZE), _genreID, movielanguageid,Integer.toString(PAGE_NUMBER)); //showPage(); } protected void nextPage() { _currentPage++; Symbol symbol = (Symbol) new Symbol(_genreID, _movielanguageID, Integer .toString(_currentPage)).clone(); if (!cache.containsKey(symbol)) { _movieDataAccess = new MovieDataAccess(this, Integer.toString(_currentPage), Integer .toString(PAGE_SIZE), _genreID, _movielanguageID,Integer.toString(PAGE_NUMBER)); } else { _movieList = (ArrayList) cache.get(symbol); cmdPreviousMovie.setLabel("<(" + (_currentPage + 1) + "/" + _totalPage + ")"); cmdNextMovie.setLabel(">(" + (_currentPage + 1) + "/" + _totalPage + ")"); cmdPreviousMovie.setVisible(_currentPage > 0); cmdNextMovie .setVisible((_currentPage + 1) * PAGE_SIZE < _totalSize); } showPage(_currentPage); } protected void prevPage() { // prevent access to -ve item number if (_currentPage > 0) { _currentPage--; Symbol symbol = (Symbol) new Symbol(_genreID, _movielanguageID, Integer.toString(_currentPage)).clone(); if (!cache.containsKey(symbol)) { _movieDataAccess = new MovieDataAccess(this, Integer.toString(_currentPage), Integer .toString(PAGE_SIZE), _genreID, _movielanguageID,Integer.toString(PAGE_NUMBER)); } else { _movieList = (ArrayList) cache.get(symbol); cmdPreviousMovie.setLabel("<(" + (_currentPage + 1) + "/" + _totalPage + ")"); cmdNextMovie.setLabel(">(" + (_currentPage + 1) + "/" + _totalPage + ")"); cmdPreviousMovie.setVisible(_currentPage > 0); cmdNextMovie .setVisible((_currentPage + 1) * PAGE_SIZE < _totalSize); } showPage(_currentPage); } } protected class NextPageCommand implements Command { public void execute() { nextPage(); } } protected class PreviousPageCommand implements Command { public void execute() { prevPage(); } } // save the movielist when have got data,storing much page once! protected void cachePage() { Iterator it = _movieList.iterator(); int iPageNum=_movieList.size()/MovieGrid.PAGE_SIZE; if(_movieList.size()%MovieGrid.PAGE_SIZE>0) iPageNum++; ArrayList[] list = new ArrayList[iPageNum]; for(int i=0;i<list.length;i++) list[i]=new ArrayList(); int iMovieNum=0; int iCurrentPage=_currentPage; int index=0; while (it.hasNext()) { MovieData movieData = (MovieData) ((MovieData) it.next()).clone(); index=iMovieNum /MovieGrid.PAGE_SIZE; iCurrentPage=_currentPage+index; list[index].add(movieData); if (iMovieNum%MovieGrid.PAGE_SIZE==0){ Symbol symbol = (Symbol) new Symbol(_genreID, _movielanguageID, Integer .toString(iCurrentPage)).clone(); //if stored beyond maxCacheSize page data,remove the earliest page. if(cache.size()>=maxCacheSize) cache.remove(0); cache.put(symbol, list[index]); } iMovieNum++; } if (_movieList.size()%MovieGrid.PAGE_SIZE!=0){ Symbol symbol = (Symbol) new Symbol(_genreID, _movielanguageID, Integer .toString(iCurrentPage)).clone(); //if stored beyond maxCacheSize page data,remove the earliest page. if(cache.size()>=maxCacheSize) cache.remove(0); cache.put(symbol, list[index]); } } } 上述构造和应用一个全局的Cache实例(OrderedMap)。
public void call() { // Establish an Async service call //final ServiceAsync service = (ServiceAsync) GWT.create(Service.class); //ServiceDefTarget target = (ServiceDefTarget) service; //target.setServiceEntryPoint(GWT.getModuleBaseURL()+ROI_SERVICE_URL); //service.call(instr, timeout, dataParameterName, jsonROIParam.toString() , roih ); if ( roiCallback != null ) { roiHandler = new ROIResponseHandler(getProxyInstruction(), roiCallback); RequestBuilder builder = new RequestBuilder( RequestBuilder.POST, GWT.getModuleBaseURL()+ROI_SERVICE_URL); try { String URLParam; // setup the proxy instruction URLParam = ROI.PROXY_INSTRUCTION + "=" + URL.encodeComponent(proxyInstruction) + "&"; URLParam += JSON_REQUEST_DTO + "=" + URL.encodeComponent(jsonROIParam.toString()) + "&"; // setting up necessary parameters builder.setTimeoutMillis(DEFAULT_TIME_OUT); builder.setHeader(HTTP_CONTENT_TYPE, HTTP_CONTENT_TYPE_VALUE); builder.setHeader(HTTP_CONNECTION, HTTP_CONNECTION_TYPE); httpResponse = builder.sendRequest(URLParam, roiHandler); } catch (RequestException e) { // if I have an exception, on client side, there is nothing I can do! // Window.alert("Failed to send the request: " + e.getMessage()); } } // else - callback is not defined, don't do the call } 上述描述了Rpc call 采用 RequestBuilder,以方便统一处理.
/** * Page is the abstract class that handles displaying and validation of a page. * Each page is a singleton object and has an associated PageInfo class. This * PageInfo class is responsible for instantiating each Page. */ public abstract class Page extends Composite implements HasDataListeners { protected String _token = ""; // _token is the associated history token for this page protected Screen _parent = null; // _parent is the screen that contains this page protected ScreenData _data = null; // _data contains all of the server variables for this page protected ArrayList _dataNames = new ArrayList(); // _dataNames is all the names of the maintained variables for this page ... /** * PageInfo is an abstract class that is responsible for accessing each Page * object. Each page should have it's own PageInfo derived class that * implements createInstance. If the page is an entry point for the * associated screen, it should overload isEntryPage(), returning true. For * example, the first step in the SignUpMember screen is the only entry page * for that screen. */ public abstract static class PageInfo { protected Page _instance; protected String _token; // history token display on the browser's address bar protected Screen _parent; // the screen that owns this... public PageInfo(String token, Screen parent) { _token = token; _parent = parent; } public abstract Page createInstance(); public String getToken() { return _token; } public String getFullHistoryToken() { return _parent.getName() + CommonDefn.HTML_SCREEN_SEPERATOR + getToken(); } public final Page getInstance() { if (_instance == null) { _instance = createInstance(); } return _instance; } /** * Indicates whether or not the associated page can be navigated to * directly from another screen. * Override this method for all entry pages. * * Basically, if 'false' it also means that the page is not allowed to be book-marked. */ public boolean isEntryPage() { return false; } } } 在Screen class也有上述类似代码,每个Page属于一个相关Screen, 一个Screen有当前Page,Screen也负责show / hide Page. 充分利用了组合和装饰。
public class DataTable extends Composite { private static final String CSS_DATATABLE = "datatable"; private static final String CSS_DATATABLE_ROW_HEADER = "datatable-row-header"; private static final String CSS_DATATABLE_ROW_EVEN = "datatable-row-even"; private static final String CSS_DATATABLE_ROW_ODD = "datatable-row-odd"; public class ColumnInfo { private static final int COL_LABEL = 0; private static final int COL_BUTTON = 1; private String _name; private String _styleName; private String _dataName; private int _colType; private HashMap _dataMap; public ColumnInfo(String name, String styleName, String dataName, int colType, HashMap dataMap) { _name = name; _styleName = styleName; _dataName = dataName; _colType = colType; _dataMap = dataMap; } public String getDataName() { return _dataName; } public String getName() { return _name; } public String getStyleName() { return _styleName; } public String getLabel(String value) { if (_dataMap != null) { return (String)_dataMap.get(value); } return value; } public boolean isButton() { return _colType == COL_BUTTON; } public boolean isLabel() { return _colType == COL_LABEL; } } public interface DataTableButtonCommand extends Command { public DataTableButtonCommand clone(); public String getParam(); public void setParam(String param); } protected int _tableSize = 0; protected ArrayList _columnInfo = null; protected FlexTable _table = null; public DataTable(int tableSize) { _tableSize = tableSize; _columnInfo = new ArrayList(); _table = new FlexTable(); // Insert the header row _table.insertRow(0); _table.getRowFormatter().addStyleName(0, CSS_DATATABLE_ROW_HEADER); _table.setStyleName(CSS_DATATABLE); initWidget(_table); } public void addColInfo(String name, String styleName, String dataName) { addColInfo(name, styleName, dataName, (HashMap)null); } public void addColInfo(String name, String styleName, String dataName, HashMap dataMap) { ColumnInfo colInfo = new ColumnInfo(name, styleName, dataName, ColumnInfo.COL_LABEL, dataMap); _columnInfo.add(colInfo); int newColNum = _table.getCellCount(0); OverflowLabel headerLabel = new OverflowLabel(name, OverflowLabel.LABEL_NONE); _table.setWidget(0, newColNum, headerLabel); String cellStyleName = CSS_DATATABLE + "-" + colInfo.getStyleName(); setStyleName(headerLabel.getElement(), cellStyleName, true); for (int row=1; row<=_tableSize; ++row) { OverflowLabel label = new OverflowLabel("", OverflowLabel.LABEL_ELLIPSES | OverflowLabel.LABEL_POPUP); _table.setWidget(row, newColNum, label); _table.getCellFormatter().addStyleName(row, newColNum, styleName); if (newColNum == 0) { String rowStyle = (row % 2 == 0) ? CSS_DATATABLE_ROW_EVEN : CSS_DATATABLE_ROW_ODD; _table.getRowFormatter().addStyleName(row, rowStyle); label.setHTML(" "); } } } public void addColInfo(String label, String styleName, String dataName, DataTableButtonCommand command) { ColumnInfo colInfo = new ColumnInfo(label, styleName, dataName, ColumnInfo.COL_BUTTON, null); _columnInfo.add(colInfo); int newColNum = _table.getCellCount(0); OverflowLabel headerLabel = new OverflowLabel(label, OverflowLabel.LABEL_NONE); _table.setWidget(0, newColNum, headerLabel); String cellStyleName = CSS_DATATABLE + "-" + colInfo.getStyleName(); setStyleName(headerLabel.getElement(), cellStyleName, true); for (int row=1; row<=_tableSize; ++row) { CommandPushButton button = new CommandPushButton(label); button.setCommand(command.clone()); button.setVisible(false); _table.setWidget(row, newColNum, button); _table.getCellFormatter().addStyleName(row, newColNum, styleName); if (newColNum == 0) { String rowStyle = (row % 2 == 0) ? CSS_DATATABLE_ROW_EVEN : CSS_DATATABLE_ROW_ODD; _table.getRowFormatter().addStyleName(row, rowStyle); } } } protected void populateRow(int rowNum, JSONValue rowValue) { JSONObject row = rowValue.isObject(); assert(row != null); for (int col=0; col<_columnInfo.size(); ++col) { ColumnInfo colInfo = (ColumnInfo)_columnInfo.get(col); String value = JSONHelper.getString(row, colInfo.getDataName()); if (colInfo.isLabel()) { OverflowLabel label = null; label = (OverflowLabel)_table.getWidget(rowNum+1, col); value = colInfo.getLabel(value); label.setHTML(value); } else if (colInfo.isButton()) { CommandPushButton button = null; button = (CommandPushButton)_table.getWidget(rowNum+1, col); button.setVisible(true); DataTableButtonCommand command = null; command = (DataTableButtonCommand)button.getCommand(); command.setParam(value); } else { assert false; } } } public void populateTable(JSONValue rowValues) { JSONArray rows = rowValues.isArray(); assert(rows != null); for (int row=0; row<rows.size() && row<20; ++row) { populateRow(row, rows.get(row)); } clearEmpties(rows.size()); } protected void clearEmpties(int emptyStart) { for (int emptyRow=emptyStart; emptyRow<_tableSize; ++emptyRow) { for (int col=0; col<_columnInfo.size(); ++col) { ColumnInfo colInfo = (ColumnInfo)_columnInfo.get(col); Widget widget = _table.getWidget(emptyRow+1, col); if (colInfo.isButton()) { widget.setVisible(false); } else if (colInfo.isLabel()) { OverflowLabel label = (OverflowLabel)widget; label.setHTML(" "); } } } } } 上述的CommandPushButton has-a push command,通过这个桥,就把实现类和DataTableButtonCommand 连接起来了.
该模式是UI中最常见的,凡是xxxListern基本都是. 问题: 在页面数据较多的页面,当用户Forward first,then go back,将被迫再次从server取数据. 情景: 再我们的preShowPage中,写有很多fetch data from server. 这里在go back 时也会执行。 解决方案: hook go back! Add history listern. 当前浏览地址发生变化的时候通知我。 public class MovieByGenrePage extends Page implements HistoryListener { private static final String CSS_RENTAL_MOVIEBYGENRE = "rental-moviebygenre"; private static final String CSS_RENTAL_MOVIEBYRENREGRID = "rental-moviebygenregrid"; private MovieGrid _movieGrid = null; private HTML _title; private String _from = ""; //record the html referrer. private static final String FROM = CommonDefn.SCREEN_RENTAL + CommonDefn.HTML_SCREEN_SEPERATOR + CommonDefn.PAGE_MOVIE_GENRE; private static final String CHECKOUT=CommonDefn.SCREEN_RENTAL + CommonDefn.HTML_SCREEN_SEPERATOR +CommonDefn.PAGE_COMPLETED_RENTAL; public static PageInfo init(final Screen parent,final ScreenData data) { return new PageInfo( CommonDefn.PAGE_MOVIE_BY_GENRE, parent ) { public Page createInstance() { return new MovieByGenrePage( CommonDefn.PAGE_MOVIE_BY_GENRE,parent, data ); } }; } public MovieByGenrePage(String token, Screen parent, ScreenData data) { super(token, parent, data); History.addHistoryListener(this); //i want to know this page came from which. // A vertical layout. VerticalPanel pageContentPanel = new VerticalPanel(); // Title { _title = new HTML(""); pageContentPanel.add( _title ); pageContentPanel.setCellHorizontalAlignment( _title, VerticalPanel.ALIGN_CENTER ); _movieGrid = new MovieGrid(data,data.getValue( Rental.DATA_RENTAL_GENRESELECTION_GENREID ),_data.getValue( Rental.DATA_RENTAL_MOVIELANGUAGESELECTION_MOVIELANGUAGEID ) ); _movieGrid.setStyleName( CSS_RENTAL_MOVIEBYRENREGRID ); pageContentPanel.add( _movieGrid ); pageContentPanel.setCellHorizontalAlignment( _movieGrid, VerticalPanel.ALIGN_CENTER ); } initWidget( pageContentPanel ); setStyleName( CSS_RENTAL_MOVIEBYGENRE ); } //just implement the function of the html referrer public void onHistoryChanged( String historyToken ){ // from is the referrer of this page,to is this page! _from=historyToken; //System.out.println("_from="+_from); // if the referrer page is mainmenu or the default screen, reset the genreid. if ( _from.equals(CommonDefn.SCREEN_DEFAULT_STARTUP) || _from.equals(CommonDefn.SCREEN_DEFAULT_STARTUP + CommonDefn.HTML_SCREEN_SEPERATOR) || _from.equals(CommonDefn.SCREEN_MAIN_MENU)) { _data.setValue(Rental.DATA_RENTAL_GENRESELECTION_GENREID,"",null); //System.out.println(_from); } } public void preShowPageExecute() { if ( _data.getValue(Rental.DATA_RENTAL_GENRESELECTION_GENREID).equals("") ) { // the first time I have not gotton it from server yet, that's why I hard-coded the default here... _data.setValue(Rental.DATA_RENTAL_GENRESELECTION_GENREID,CommonDefn.DEFAULT_GENRE_ID,null); } if ( _data.getValue(Rental.DATA_RENTAL_MOVIELANGUAGESELECTION_MOVIELANGUAGEID).equals("") ) { _data.setValue( Rental.DATA_RENTAL_MOVIELANGUAGESELECTION_MOVIELANGUAGEID,CommonDefn.DEFAULT_MOVIE_LANGUAGE_ID,null ); } if ( _from.length()<1 || _from.equals(FROM) || _from.equals(CHECKOUT) || _from.equals(CommonDefn.SCREEN_MAIN_MENU)) { //_from.length()<1,it's screen redire to this page! another flow is from search to this page, otherwise it's goto this page. //for goto we do nothing,just show it's orignal model nor fetch data from server. _movieGrid.updateGenreAndLanguageID( _data.getValue( Rental.DATA_RENTAL_GENRESELECTION_GENREID ),_data.getValue( Rental.DATA_RENTAL_MOVIELANGUAGESELECTION_MOVIELANGUAGEID ) ); } } 另外利用ChangeListener,ChangeListenerCollection,fireChange 制作一个mvc模式的Validator组件也是必要的. [list=]3.2 Command[/list] 原型: Interface Command { public void execute(); } 问题: 可能需要定时任务. 情景: Context help content -tips 需要定时关闭(其他地方也需要定时任务,但任务可能未知:( ) 解决方案: 只要任务实现 Command,然后动态传入Command 实例即可. public Trigger extends Timer implements Command{ private int _delay; private Command _command; public Trigger(int delay,Command command){ _delay = delay; _command = command; } public void run() { _command.execute(); } public void execute(){ this.schedule(_delay); } } Trigger既实现了一个对外的公开接口,也提供一个constructor 接口.所以,有时候方便别人也是方便自己. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |