<作用>
将对象组合成树形结构以表示“部分-整体”的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有唯一性
<结构图>
涉及角色:
最近在做基于Mondrin的olap统计分析时,需要事先在xml文件中定义好mdx语言查询语句,而且该mdx语言查询语句还是树形结构,所以想到使用组合模式来配合完成。具体代码如下:
XML文件格式如下:
<?xml version="1.0" encoding="UTF-8"?> <mondrain type="mon"> <!-- 配置报表类型的MDX语句 配置方式:地区($area) 、年份($year) 、月份($month) type表示数据类型,contentData:内容数据、titleData:头部数据、comb0~9:组合数据 --> <report type="NewActiveUser" name="新增活跃数"> <title> <param type="T" name="新增活跃数的表头">select {[地区].[$area].Children} ON columns,{{[时间].[$year]}*{[Measures].[到达目标]}} ON rows from [ACTIVE_GOAL]</param> </title> <content> <param type="C" name="新增活跃数据">WITH MEMBER [Measures].[同比新增] AS '([Measures].[新增活跃用户]-ParAllelPeriod(Year,1))/ParAllelPeriod(Year,1)',FORMAT_STRING='##.00%' MEMBER [Measures].[年度到达] AS 'Measures.[年度到达].Parent+[Measures].[新增活跃用户]'select {[地区].[$area].Children} ON columns, {{[时间].[$year].Children}*{[Measures].[新增活跃用户],[Measures].[同比新增],[Measures].[年度到达]}} ON rows from [Active] </param> </content> </report> <report type="deal" name="交易额"> <title> <param type="T" name="交易额目标">WITH MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'select AddCalculatedMembers([地区].[$area].Children) ON columns,{{[时间].[$year]}*{[Measures].[到达数目标值]}}ON rows from [Active_Goal] where [发展目标].[101138].[10113804]</param> </title> <content> <param type="C" name="组合数据"> <parm type="L" name="交易额查询不带个人消费 "> WITH MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'select AddCalculatedMembers([地区].[$area].Children) ON columns, {{[时间].[$year].[$month]}*{[Measures].[控制台充值金额],[Measures].[POS充值金额],[Measures].[网上充值金额],[Measures].[短信充值金额],[Measures].[IVR充值金额],[Measures].[OTA充值金额],[Measures].[WAP充值金额],[Measures].[营业厅充值金额],[Measures].[客户端充值金额],[Measures].[代扣充值金额],[Measures].[TSM充值金额],[Measures].[IPTV充值金额],[Measures].[添益宝充值金额],[Measures].[手机收银台充值金额],[Measures].[控制台消费金额],[Measures].[POS消费金额],[Measures].[网上消费金额],[Measures].[短信消费金额],[Measures].[IVR消费金额],[Measures].[客户端消费金额],[Measures].[代扣消费金额],[Measures].[TSM消费金额],[Measures].[IPTV消费金额],[Measures].[OTA消费金额],[Measures].[WAP消费金额],[Measures].[营业厅消费金额],[Measures].[添益宝消费金额],[Measures].[手机收银台消费金额]}} ON rows from [deal_cube] </parm> <parm type="L" name="交易汇总"> WITH MEMBER [地区].[$area].[合计] AS [地区].[$area].Parent select AddCalculatedMembers([地区].[$area].Children) ON columns, {{[时间].[$year].[$month]}*{[Measures].[企业账户资金归集],[Measures].[网关交易],[Measures].[积分],[Measures].[营业厅POS收单]}} ON rows from [deal_total_cube] </parm> </param> </content> </report> <report type="AccountNum" name="账户数"> <title> <param type="T" name="账户数表头">WITH MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'MEMBER [Measures].[上年到达数目标值] AS 'Sum({ParAllelPeriod(Year,1,[时间].[$year].[12])}*{[Measures].[年度到达]})'MEMBER [Measures].[净增目标] AS '[Measures].[到达目标]-[Measures].[上年到达数目标值]'select AddCalculatedMembers([地区].[$area].Children) ON columns,{{[时间].[$year]}*{[Measures].[到达目标],[Measures].[净增目标]}}ON rows from [ACCOUNT_NUM_GOAL]</param> </title> <content> <param type="C" name="账户数数据">WITH MEMBER [Measures].[净增开户] AS '([Measures].[本月新增开户数]-[Measures].[销户数])' MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'select AddCalculatedMembers([地区].[$area].Children) ON columns, {{[时间].[$year].[$month]}*{[Measures].[净增开户],[Measures].[年度到达]}} ON rows from [ACCOUNT_NUM]</param> </content> </report> <report type="businesspos" name="商户POS交易"> <title > <param type="T" name="POS商户交易情况" >WITH MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'select AddCalculatedMembers([地区].[$area].Children) ON columns,{{[时间].[$year]}*{[Measures].[到达数目标值]}}ON rows from [Active_Goal] where [发展目标].[101138].[10113804]</param> </title> </report> </mondrain>
package cn.my.da.service.olap.DataCollection.readconfig; /** * 组合对象 * @author xiaowen * @date 2016年5月26日 * @ version 1.0 */ public abstract class Component { /** * 节点名字 */ protected String nodeName; public Component(){}; /** * 带参数的构造方法 * @param nodeName */ public Component(String nodeName){ this.nodeName = nodeName; } /** * 添加子节点 * @param component */ public abstract void addChildren(Component component); /** * 删除子节点 * @param component */ public abstract void removeChildren(Component component); /** * 是否还有下一个子节点 * boolean */ public abstract boolean hasNextChlidNode(); }
package cn.my.da.service.olap.DataCollection.readconfig; /** * 叶节点对象。叶子节点没有子节点 * @author xiaowen * @date 2016年5月26日 * @ version 1.0 */ public class Leaf extends Component { public Leaf(String nodeName) { super(nodeName); } @Override public void addChildren(Component component) { System.out.println("Can not add to a leaf"); } @Override public void removeChildren(Component component) { System.out.println("Can not remove from a leaf"); } @Override public boolean hasNextChlidNode() { return false; } }Composite类:
package cn.my.da.service.olap.DataCollection.readconfig; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 定义枝节点的行为 * @author xiaowen * @date 2016年5月26日 * @ version 1.0 */ public class Composite extends Component { /** * 存储子节点集合 */ private List<Component> children = new ArrayList<Component>(); /** * 存储节点数据 */ private Map<String, String> nodeAttribute; /** * 不带参数的构造方法 */ public Composite(){}; /** * 带参数的构造方法 * @param nodeName * @param nodeAttribute */ public Composite(String nodeName,Map<String, String> nodeAttribute) { super(nodeName); this.nodeAttribute = nodeAttribute; } public Composite(String nodeName, List<Component> children, Map<String, String> nodeAttribute) { super(nodeName); this.children = children; this.nodeAttribute = nodeAttribute; } @Override public void addChildren(Component component) { this.children.add(component); } @Override public void removeChildren(Component component) { this.children.remove(component); } @Override public boolean hasNextChlidNode() { return children.isEmpty()&&children.size()==0?true:false; } /** * @return the children */ public List<Component> getChildren() { return children; } /** * @param children the children to set */ public void setChildren(List<Component> children) { this.children = children; } /** * @return the nodeAttribute */ public Map<String, String> getNodeAttribute() { return nodeAttribute; } /** * @param nodeAttribute the nodeAttribute to set */ public void setNodeAttribute(Map<String, String> nodeAttribute) { this.nodeAttribute = nodeAttribute; } }
读取配置文件的接口IReadConfig
package cn.my.da.service.olap.DataCollection.readconfig; /** * 读取配置文件的接口 * @author xiaowen * @date 2016年5月26日 * @ version 1.0 */ public interface IReadConfig { /** * 初始化配置文件 * @param configName * @return * Component */ public Component inintConfigByName(String configName); /** * 根据节点名获取对象 * @param configNodeName 节点名称 * @return * Component */ public Component getComponent(String configNodeName); }读取配置文件的实现接口IReadConfigImpl:
package cn.my.da.service.olap.DataCollection.readconfig; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import cn.my.da.service.olap.DataCollection.utils.StaticConstant; /** * 读取配置文件的实现类 * @author xiaowen * @date 2016年5月26日 * @ version 1.0 */ public class IReadConfigImpl implements IReadConfig{ /** * 组合对象 */ private Composite composite; /** * 存储title和content数据 */ private Map<String, Composite> map = new LinkedHashMap<String, Composite>(); @Override public Component inintConfigByName(String configName) { try { // 创建解析xml配置文件的SAX对象,基于事件驱动机制解析xml SAXReader reader = new SAXReader(); LoggerUtils.info("XML文件《" + configName + "》读取开始......"); String path = ReadConfigManagerImpl.class.getClassLoader() .getResource(configName).getPath(); // 开始读取配置文件 Document doc = reader.read(new File(path)); //创建组合对象 composite = new Composite(); // 获取根节点 Element rootElem = doc.getRootElement(); composite.setNodeName(rootElem.getName()); //循环存储xml数据结构 for (Iterator i = rootElem.elementIterator(); i.hasNext();) { Element reportElem = (Element) i.next(); Composite report = new Composite(); report.setNodeName(reportElem.getName()); report.setNodeAttribute(setConfigMapValue(reportElem)); composite.addChildren(report); for (Iterator j = reportElem.elementIterator(); j.hasNext();) { Element childElem = (Element) j.next(); Composite child = new Composite(); child.setNodeName(childElem.getName()); child.setNodeAttribute(setConfigMapValue(childElem)); report.addChildren(child); //节点的名称 String nodeName = child.getChildren().get(0).nodeName; if (nodeName.equals(StaticConstant.CONTENT_NAME)) {//内容部分 map.put(StaticConstant.CONTENT_NAME + StaticConstant.LOWER_LINE + report.getNodeAttribute().get(StaticConstant.NODE_TYPE), child); } else if (nodeName.equals(StaticConstant.TITLE_NAME)) {//表头部分 map.put(StaticConstant.TITLE_NAME + StaticConstant.LOWER_LINE + report.getNodeAttribute().get(StaticConstant.NODE_TYPE), child); } //循环子节点是否还有一个元素 for (Iterator g = childElem.elementIterator(); g.hasNext();) { Element gElem = (Element) g.next(); getElementList(gElem, child); } } } } catch (DocumentException e) { e.printStackTrace(); throw new ConfigException("读取《" + configName + "》文件失败,错误信息:" + e); } return composite; } @Override public Component getComponent(String configNodeName) { return map.get(configNodeName)!=null?map.get(configNodeName):null; } /** * 递归遍历元素 * * @param element * @param configvo */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected void getElementList(Element element, Composite composite) { Composite c1 = new Composite(element.getName()); c1.setNodeAttribute(setConfigMapValue(element)); composite.addChildren(c1); // 获取所有的节点 List<Element> elements = element.elements(); if (elements.size() == 0) {//递归临界点 System.out.println("已没有任何的元素!!!!"); } else { // 有子元素 for (Iterator it = elements.iterator(); it.hasNext();) { Element elem = (Element) it.next(); getElementList(elem, c1); // 递归遍历 } } } /** * 设置Map值 * * @param elem * @return Map<String,String> */ @SuppressWarnings("unchecked") private Map<String, String> setConfigMapValue(Element elem) { Map<String, String> map = new HashMap<String, String>(); map.put(StaticConstant.ROOT_NAME, elem.getName() != null ? elem.getName() : null); if (elem.getText() != null) { map.put(StaticConstant.GAIN_DATA_NAME, elem.getTextTrim()); } List<Attribute> at = elem.attributes(); for (int i = 0; i < at.size(); i++) { Attribute item = at.get(i); map.put(item.getName(), item.getValue()); } return map; } }
另附工具类代码:
LoggerUtils:
package cn.my.da.service.olap.DataCollection.utils; /** * 日志类 * @author wangbowen * @version 1.0 */ public class LoggerUtils { /** * log对象 */ private static org.apache.log4j.Logger log = org.apache.log4j.Logger.getRootLogger(); /** * 日志信息 * @param message 输出信息 */ final public static void info(Object message) { log.info(message); } /** * 调试信息 * @param message 输出信息 */ final public static void debug(Object message) { log.debug(message); } /** * 错误信息 * @param message 输出信息 */ final public static void error(Object message) { log.error(message); } /** * 警告信息 * @param message 输出信息 */ final public static void warn(Object message) { log.warn(message); } /** * 严重错误信息 * @param message 输出信息 */ final public static void fatal(Object message) { log.fatal(message); } }StaticConstant静态变量类:
package cn.my.da.service.olap.DataCollection.utils; /** * 静态常量类 * @author wangbowen * @version 1.0 */ public class StaticConstant { /** * 配置文件名称 */ public static final String CONFIG_NAME = "dataCollectionConfigInfo.xml"; public static final String THREAD_CONFIG_NAME ="threadConfigInfo.xml"; /** * 获取Map数据的键名 */ public static final String GAIN_DATA_NAME="data"; /** * 根节点名 */ public static final String ROOT_NAME ="rootName"; /** * 父级名称 */ public static final String CONFIG_ROOT_NAME ="mon"; /** * 子级名称 */ public static final String CHILD_NODE_NAME ="threadSet"; /** * 标识符 */ public static final String WHIPPLE_TREE ="-"; public static final String LOWER_LINE ="_"; public static final String TITLE_NAME ="title"; public static final String CONTENT_NAME ="content"; public static final String NODE_TYPE ="type"; /** * 一月 */ public static final String JANUARY = "1"; /** * 读取数据线程bean名称 */ public static final String READ_THREAD_NAME = "readDataThreadManager"; /** * 修改线程bean名称 */ public static final String WRITE_THREAD_NAME="fileDataThreadManager"; /** * 读取配置文件bean名称 */ public static final String READ_CONFIGINFO_NAME="iReadConfigManager"; /** * 休眠时间 */ public static final Long SLEEP_TIME=2500L; /** * rmi访问地址 */ public static final String RMI_ADDRESS = "rmi://192.168.4.167:8099/dataAnalysisQuery"; public static final String LOCAL_ADDRESS="rmi://localhost:8099/dataAnalysisQuery"; }
<应用场景>
1.想表示对象的部分-整体层次结构
2.希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
<应用例子>
1.文件系统目录结构
2.网站导航结构
3.公司内各部门的层级关系