Dorado重用最佳实践

   在实际开发中我们都会面临组件重用的问题, 面向对象的语言可以通过各种设计模式来实现重用.那么在dorado框架中如何进行重用呢? 对于dorado的重用问题,下面我们从三个方面来进行说明.
1.通过使用jsp:include指令进行重用
将要重用的内容写到一个jsp页面中, 然后通过jsp:include标记在需要使用的位置进行重用, 该jsp中可再引用d:view标签,比如定义一个jsp页面:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://www.bstek.com/dorado" prefix="d"%>
<d:View config="com.baiyao.workflow.component.ChargeType">
	<d:SubWindow id="winChargeType" title="选择类型" width="300" height="200"
		draggable="true" resizable="true" status="hidden"
		showMinimizeButton="false" showMaximizeButton="false"
		showCloseButton="true">
		<d:Layout type="border" width="100%" height="100%">
			<d:Pane position="center">
				<d:DataTable id="tblType" />
			</d:Pane>
			<d:Pane position="bottom" align="center">
				<d:Button id="btnOK" />
			</d:Pane>
		</d:Layout>
	</d:SubWindow>
</d:View>


然后在另外一个页面中进行引入:
<d:View config="com.baiyao.workflow.charge.ChargeInput">
	<d:AutoForm id="frmForm">
		<d:FormElementRenderer group="group1" element="render1">
			<d:Layout type="border">
				<d:Pane position="center">
					<d:DataTable id="tblFormItem" />
				</d:Pane>
				<d:Pane position="bottom" align="center">
					<d:Button id="btnAdd" />&nbsp;&nbsp;
					<d:Button id="btnDelete" />
				</d:Pane>
			</d:Layout>
		</d:FormElementRenderer>
	</d:AutoForm>
	<jsp:include page="/workflow/component/win_charge_type.jsp" />
</d:View>

该重用方式比较原始, 主要是利用了jsp:include指令, 会编写jsp的人都会用, 相对来说还是非常简单的. 比较适合组件在页面中的布局中规中矩的情况, 比如实现某一类功能的组件都集中在页面中的某一区域.如果某一类功能的组件在页面中的分布跨度比较大的话, 则会因为没法封装在一个jsp中而无法实现重用.由于重用范围仅限于jsp页面中, 因此其维护性还是非常不错的.但是如果对单个简单组件专门写一个jsp从而在多个jsp页面重用显然有些得不偿失.而且如果对于page1页面中适用到了conrol1,conrol2,conrol3几个组件, 同样在page2页面中也有这3个组件, 大部分属性都差不多, 但是只有少数几个属性不同, 那么我们只能对jsp进行重用, 而jsp对应的view.xml文件则需要写两个. 因此重用性会大一定的折扣
易用程度:★★★★★
适用范围:★★★☆☆
重用粒度:★★☆☆☆
可维护性:★★★★★

2.对view组件定义进行重用
view重用分为两种, 一种是通过调用Server API来创建客户端所需要的组件, 另外一种就是将多个view.xml中定义的组件进行组合

2.1.通过调用Server API来创建客户端所需要的组件
通过调用Server API使用java代码在后台来来创建页面所需要的各种Control和Dataset, 而不是在view.xml文件中定义需要的control和dataset, 因为采用这种方式就可以通过利用java的OO特性(封装, 继承和多态), 比如一个保存按钮, 可能在一个涉及到编辑操作的页面中都会用到, 因此我们可以创建一个ViewModel基类,在基类的initControls()方法中通过new Button()的方式来创建该按钮, 然后指定按钮的属性, 有时间的还要加上事件的javascript脚本, 这样所有从该基类继承的ViewModel所对应的view所在的页面都可以适用该按钮了.
@Override
protected void initControls() throws Exception {
	Button btnSave = (Button) createControl("Button", "btnSave");
	btnSave.setCommand("cmdSave");
	btnSave.setValue("保存");
	btnSave.setWidth("70");

	UpdateCommand cmdSave= (UpdateCommand) createControl(
			"UpdateCommand", "cmdSave");
	cmdSave.setMethod("save");
	DatasetInfo info = cmdSave.addDatasetInfo("dsEntity");
	info.setSubmitScope(DatasetInfo.ALL_CHANGE);
	return btnSave;
}


该重用方式实际上就是将在view.xml配置文件中定义的control或者dataset通过java代码来实现, 实际上是换了一种写法而已, 但是相对于配置文件中的定义来说, 不够直观, 而且要写更多java的代码, 如果该该组件比较复杂的话, 比如写有复杂的事件代码, 则需要去拼javascript字符串脚本, 这样维护性将非常差.但是这种方式的重用粒度非常细, 比如可以只对一个button中的某几个属性进行重用. 而且对于基类定义的组件可以根据需要进行重载, 这样灵活性将非常好.
易用程度:★★★☆☆
适用范围:★★☆☆☆
重用粒度:★★★★★
可维护性:★☆☆☆☆

2.2.多个view.xml中定义的组件进行组合重用
在前面介绍jsp重用方式的时候, 我们知道是将一个jsp页面分解成多个页面来进行重用, 而这里我们换一个角度: 将一个view.xml根据重用的需要分解成多个view.xml文件, 其实在jsp重用中, 也使用了多个view.xml文件(进行了view.xml的分解), 但是二者的合并时机是不同的, jsp重用是在jsp页面的时候做的view.xml合并, 而这里的合并是在ViewModel初始化中进行的.
这里先来介绍一下JSP Dorado Taglib, ViewModel和view.xml之间的关系(仅限个人理解)
在dorado的view tag中, 根据给定的view文件所在的位置, 创建ViewModel对象, 该对象中会包含一个ViewModelConfig, 它是对应的view.xml文件的一个解析, 接着进行ViewModel的初始化工作, 初始化就是实际创建组件的过程, 这里面有一些细节我们这里不做研究(比如对于不同的control, 创建的时机是不同的). 最后得到的是一个个组件的Java对象, 我们可以将其看成一个个javabean, jsp页面上的dorado taglib就会根据这些组件对象来生成html或者js脚本.
三者之间的关系大致是这样, 接着我们开始探讨将多个view.xml合并的时机, 最开始我采用在生成ViewModel的时候将要组合的其他view.xml引入, 来生成最终我需要的ViewModelConfig对象(在原有基础上添加了其他view.xml中的组件), 后来这种做法失败了(这个过程太多复杂, 比如还涉及到缓存问题, 很容易出现在第一次展现没有问题, 但是在通过command发送ajax请求找不到对应的对象而出错), 于是我在ViewModel初始化(就是init方法)的时候将要组合的view.xml引入进去, 这时候成功了, 没有出现问题.可能dorado原来的设计没有考虑到合并多个view.xml的做法, 因此ViewModel在这一方面还是很封闭的, 很多相关的方法都是private的, 因此需要copy出来
public class BaseViewModel extends DefaultViewModel {
	protected static ControlFactory controlFactory;
	protected List<ViewModelConfig> compositedViewModelConfigs;
	protected List<String> compositedConfigNames;
	private int state;

	@Override
	public void init(int state) throws Exception {
		List<String> result = new ArrayList<String>();
		addViewModelConfig(result);

		// 因为在初始化其他viewModelConfig的时候需要使用到状态, 而此时还没有执行super的init方法,
		// 因此sate还是最初的STATE_VIEW状态
		setState(state);

		initCompositedViewModelConfigs(state, result);
		super.init(state);
	}

	protected void setState(int state) {
		this.state = state;
	}

	public int getState() {
		return state;
	}

	/**
	 * 添加需要组合的view.xml文件路径
	 * 
	 * @param result
	 */
	protected void addViewModelConfig(List<String> result) {
		result.add(JbpmConstants.VIEW_TASK_INSTANCE);
	}

	/**
	 * 根据ViewModelConfig初始化创建组件
	 * 
	 * @param state
	 * @param configNames
	 * @throws Exception
	 */
	protected void initCompositedViewModelConfigs(int state,
			List<String> configNames) throws Exception {
		for (String configName : configNames) {
			ViewModelConfig config = getViewModelConfig(configName);

			loadDatasetConfigs(config);
			loadControlConfigs(config);
			if (state == STATE_VIEW) {
				loadEventConfig(DoradoContext.getContext(), config);
			}
		}
	}

	/**
	 * 根据view.xml文件名得到ViewModelConfig对象
	 * 
	 * @param configName
	 * @return
	 * @throws Exception
	 */
	protected ViewModelConfig getViewModelConfig(String configName)
			throws Exception {
		ViewModelConfig config = null;
		if (compositedViewModelConfigs == null) {
			compositedViewModelConfigs = new ArrayList<ViewModelConfig>();
			compositedConfigNames = new ArrayList<String>();
		}
		if (!compositedConfigNames.contains(configName)) {
			ViewModel viewModel = ViewModelManager.getViewModel(null,
					configName, getNamespace(), "request");
			config = viewModel.getConfig();
			compositedViewModelConfigs.add(config);
			compositedConfigNames.add(configName);
		}
		return config;
	}

	@SuppressWarnings("unchecked")
	protected static ControlFactory getControlFactory() {
		if (controlFactory == null)
			try {
				String clazz = Setting.getString("view.controlFactory");
				Class cl = Class.forName(clazz);
				controlFactory = (ControlFactory) cl.newInstance();
			} catch (IllegalAccessException ex) {
				Log.error(ex);
			} catch (InstantiationException ex) {
				if (System.getProperty("java.version").compareTo("1.4") >= 0)
					Log.error(ex.getCause());
				else
					Log.error(ex);
			} catch (ClassNotFoundException ex) {
				Log.error(ex);
			}
		return controlFactory;
	}

	@SuppressWarnings("unchecked")
	protected void loadDatasetConfigs(ViewModelConfig viewModelConfig)
			throws Exception {
		if (viewModelConfig == null)
			return;
		List keys = viewModelConfig.datasetNodes();
		int count = keys.size();
		for (int i = 0; i < count; i++) {
			String id = (String) keys.get(i);
			XmlNode node = viewModelConfig.getDatasetNode(id);
			if (state != 2 && state != 3)
				createDataset(node);
		}

	}

	@SuppressWarnings("unchecked")
	protected void loadControlConfigs(ViewModelConfig config) throws Exception {
		if (config == null)
			return;
		List keys = config.controlNodes();
		int count = keys.size();
		for (int i = 0; i < count; i++) {
			String id = (String) keys.get(i);
			XmlNode node = config.getControlNode(id);
			String type = node.getAttribute("type");
			Class typeClass = getControlFactory().getControlType(type);
			if (typeClass != null) {
				if ((com.bstek.dorado.view.control.Logical.class)
						.isAssignableFrom(typeClass)) {
					createControl(type, id);
					continue;
				}
				if (state == STATE_VIEW
						&& !(com.bstek.dorado.view.control.Placeable.class)
								.isAssignableFrom(typeClass))
					createControl(type, id);
			} else {
				throw new IllegalArgumentException("Unknown control type '"
						+ type + "'!");
			}
		}

	}

	protected void loadEventConfig(DoradoContext context, ViewModelConfig config) {
		if (config == null)
			return;
		XmlNode eventNodes[] = null;
		XmlNode eventsNode = config.getRoot().getChild("Events");
		if (eventsNode != null)
			eventNodes = eventsNode.getChildren();
		if (eventNodes != null) {
			for (int i = 0; i < eventNodes.length; i++) {
				XmlNode eventNode = eventNodes[i];
				String script = XmlConfigUtils.getNodeContent(eventNode,
						context);
				EventHandler event = new EventHandler(eventNode
						.getAttribute("name"), script);
				addEventHandler(event);
			}

		}
	}

	public Control getControl(String id) throws Exception {
		ViewModelConfig config = getConfig();
		Control control = getControl(config, id, true);
		return control;
	}

	/**
	 * 在多个view.xml文件中遍历直到找出要对应的javabean模型数据来创建control
	 * 
	 * @param config
	 * @param id
	 * @param loop
	 *            是否循环查找, 如果是在compositeViewModelConfigs中查找的话应该避免循环查找, 否则会死循环
	 * @return
	 * @throws Exception
	 */
	private Control getControl(ViewModelConfig config, String id, boolean loop)
			throws Exception {
		Control control = (Control) controls.get(id);
		if (control == null && config != null) {
			XmlNode node = config.getControlNode(id);
			if (node != null) {
				String type = node.getAttribute("type");
				control = createControl(type, id);
			} else if (loop) {
				// 注意顺序, 添加组合的view.xml文件的原则是后添加的同id的control或dataset将覆盖前面的
				for (int i = compositedViewModelConfigs.size() - 1; i >= 0; i--) {
					compositedViewModelConfigs.get(i);
					control = getControl(compositedViewModelConfigs.get(i), id,
							false);
					if (control != null) {
						break;
					}
				}
			}
		}
		return control;
	}

	public Control createControl(String type, String id) throws Exception {
		Control control = (Control) controls.get(id);
		if (control == null) {
			control = constructControl(type, id);
			ViewModelConfig config = getConfig();
			if (config != null) {
				initControl(control, config, id);
			}
			controls.forceAdd(id, control);
			initControl(control);
		}
		return control;
	}

	private void initControl(Control control, ViewModelConfig config, String id)
			throws Exception {
		XmlNode node = config.getControlNode(id);
		if (node == null) {
			for (int i = compositedViewModelConfigs.size() - 1; i >= 0; i--) {
				config = compositedViewModelConfigs.get(i);
				node = config.getControlNode(id);
				if (node != null) {
					break;
				}
			}
		}
		control.init(DoradoContext.getContext(), node);
	}

	protected ViewDataset createDataset(String type, String id, XmlNode node)
			throws Exception {
		ViewDataset dataset = constructDataset(type, id);
		DoradoContext context = DoradoContext.getContext();
		if (node != null) {
			dataset.init(context, node);
			if (state == STATE_REPORT)
				dataset.setAutoLoadData(true);
		}
		datasets.forceAdd(id, dataset);
		initDataset(dataset);
		return dataset;
	}

	public ViewDataset createDataset(XmlNode node) throws Exception {
		String type = node.getAttribute("type");
		String id = node.getAttribute("id");
		return createDataset(type, id, node);
	}

	@Override
	public ViewDataset getDataset(String id) {
		// 在当前config中找, 如果找不到, 将在组合config中去找
		ViewDataset dataset = super.getDataset(id);
		if (dataset == null) {
			for (ViewModelConfig config : compositedViewModelConfigs) {
				if (dataset == null && config != null) {
					XmlNode node = config.getDatasetNode(id);
					if (node != null)
						try {
							String type = node.getAttribute("type");
							dataset = createDataset(type, id);
							if (dataset != null)
								break;
						} catch (Exception ex) {
							Log.error(ex);
						}
				}
			}
		}
		return dataset;
	}

	@Override
	public ViewDataset createDataset(String type, String id) throws Exception {
		XmlNode node = null;
		ViewModelConfig config = getConfig();
        if(config != null) {
        	node = config.getDatasetNode(id);
        	if (node == null) {
        		for (ViewModelConfig vmc : compositedViewModelConfigs) {
        			node = vmc.getDatasetNode(id);
        			if (node != null) {
        				break;
        			}
				}
        	}
        }
        return createDataset(type, id, node);
	}
}

该重用方式集成了jsp重用的优点, 又在一定程度上消除了它的缺点, 与jsp重用相比, 其优点在于, 它不会受到组件在页面中的位置布局的影响. 在重用粒度上能对单个的组件进行重用, 相比jsp重用要细, 但是比Server API的重用方式要粗一些. 因为只是配置文件上的重用, 因此主要是对配置文件的维护, 可维护性要比Server API方式要好, 该方式需要使用者对view.xml配置文件, jsp dorado taglib, ViewModel类三者之间的关系有非常好的认识.
易用程度:★★☆☆☆
适用范围:★★★★☆
重用粒度:★★★★☆
可维护性:★★★★★

其他重用技巧
如果view.xml文件都一样(说明界面一样), 只是ViewModel不同(说明后台业务逻辑不同)的情况下, 我们可以让其公用同一个view.xml, 只是在d:view的配置上加上clazz属性指定二者不同的ViewModel即可.比如这样的写法:
<d:View config="com.baiyao.workflow.settlement.SettlementInput"
	clazz="com.baiyao.workflow.settlement.ExpenseSettlementInputViewModel">
	<jsp:include page="/workflow/settlement/settlement_input.jsp" />
</d:View>


如何选择
其实这几种重用方式互相之间并不矛盾, 可以在一个功能模块中根据需要结合起来适用.但是如果使用太多的重用方式, 会提高项目的复杂程度, 这样就会影响到可维护性, 因此重用也应该适可而止, 否则就是过犹不及.

你可能感兴趣的:(JavaScript,jsp,xml,脚本,OO)