论坛首页 Web前端技术论坛

设计模式在gwt中的应用

浏览 2968 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-12-10  
GWT
设计模式在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)
顺序进行描写。不求一一覆盖,只为抛砖引玉,期望和大家一起交流



    1.1 SingleTon

    问题: 目前项目有一个带分页功能的电影浏览,用户在页面间切换频率很大。造成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)。



    1.2 Builder


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,以方便统一处理.


    2.1 Decorator && Composite

/**
 * 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.
充分利用了组合和装饰。


    2.2 Bridge

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 连接起来了.



    3.1 Observer

    该模式是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 接口.所以,有时候方便别人也是方便自己. 
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics