设计模式在gwt中的应用

[b][size=18]设计模式在gwt中的应用[/size][/b]


[color=olive]本文主要介绍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)
顺序进行描写。不求一一覆盖,只为抛砖引玉,期望和大家一起交流[/color]


[list][color=blue]1.1 SingleTon[/color][/list]
问题: 目前项目有一个带分页功能的电影浏览,用户在页面间切换频率很大。造成server traffic.
情景: 点上一页,下一页,都需要到服务器去取数据。
解决方案: 在客户端设置cache,第一次访问后,存储当前页数据。当存储满100页数据时,清掉最早存储的数据,以更新数据和防止memory out.


[code]/**
* 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]);

}
}

}
[/code]

上述构造和应用一个全局的Cache实例(OrderedMap)。


[list][color=blue]1.2 Builder[/color][/list]

[code]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

}
[/code]

上述描述了Rpc call 采用 RequestBuilder,以方便统一处理.


[list][color=blue]2.1 Decorator && Composite[/color][/list]
[code]/**
* 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;
}
}

}[/code]


在Screen class也有上述类似代码,每个Page属于一个相关Screen, 一个Screen有当前Page,Screen也负责show / hide Page.
充分利用了组合和装饰。


[list][color=blue]2.2 Bridge[/color][/list]
[code]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(" ");
}
}
}
}
}
[/code]


上述的CommandPushButton has-a push command,通过这个桥,就把实现类和DataTableButtonCommand 连接起来了.


[list][color=blue]3.1 Observer[/color][/list]
该模式是UI中最常见的,凡是xxxListern基本都是.

问题: 在页面数据较多的页面,当用户Forward first,then go back,将被迫再次从server取数据.
情景: 再我们的preShowPage中,写有很多fetch data from server. 这里在go back 时也会执行。
解决方案: hook go back! Add history listern. 当前浏览地址发生变化的时候通知我。


[code]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 ) );
}




}
[/code]

另外利用ChangeListener,ChangeListenerCollection,fireChange 制作一个mvc模式的Validator组件也是必要的.


[list=][color=blue]3.2 Command[/color][/list]
原型: Interface Command {
public void execute();
}


问题: 可能需要定时任务.
情景: Context help content -tips 需要定时关闭(其他地方也需要定时任务,但任务可能未知:( )
解决方案: 只要任务实现 Command,然后动态传入Command 实例即可.



[code]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);
}

}[/code]

Trigger既实现了一个对外的公开接口,也提供一个constructor 接口.所以,有时候方便别人也是方便自己. :D
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值