<作用>
将对象组合成树形结构以表示“部分-整体”的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有唯一性
<结构图>
涉及角色:
最近在做基于Mondrin的olap统计分析时,需要事先在xml文件中定义好mdx语言查询语句,而且该mdx语言查询语句还是树形结构,所以想到使用组合模式来配合完成。具体代码如下:
XML文件格式如下:
select {[地区].[$area].Children} ON
columns,{{[时间].[$year]}*{[Measures].[到达目标]}} ON rows from
[ACTIVE_GOAL]
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]
WITH MEMBER [地区].[$area].[合计] AS
'Sum([地区].[$area].Children)'select
AddCalculatedMembers([地区].[$area].Children) ON
columns,{{[时间].[$year]}*{[Measures].[到达数目标值]}}ON rows from
[Active_Goal] where [发展目标].[101138].[10113804]
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]
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]
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]
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]
WITH MEMBER [地区].[$area].[合计] AS 'Sum([地区].[$area].Children)'select AddCalculatedMembers([地区].[$area].Children) ON columns,{{[时间].[$year]}*{[Measures].[到达数目标值]}}ON rows from [Active_Goal] where [发展目标].[101138].[10113804]
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 children = new ArrayList();
/**
* 存储节点数据
*/
private Map nodeAttribute;
/**
* 不带参数的构造方法
*/
public Composite(){};
/**
* 带参数的构造方法
* @param nodeName
* @param nodeAttribute
*/
public Composite(String nodeName,Map nodeAttribute) {
super(nodeName);
this.nodeAttribute = nodeAttribute;
}
public Composite(String nodeName, List children, Map 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 getChildren() {
return children;
}
/**
* @param children the children to set
*/
public void setChildren(List children) {
this.children = children;
}
/**
* @return the nodeAttribute
*/
public Map getNodeAttribute() {
return nodeAttribute;
}
/**
* @param nodeAttribute the nodeAttribute to set
*/
public void setNodeAttribute(Map 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 map = new LinkedHashMap();
@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 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
*/
@SuppressWarnings("unchecked")
private Map setConfigMapValue(Element elem) {
Map map = new HashMap();
map.put(StaticConstant.ROOT_NAME, elem.getName() != null ? elem.getName() : null);
if (elem.getText() != null) {
map.put(StaticConstant.GAIN_DATA_NAME, elem.getTextTrim());
}
List 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.公司内各部门的层级关系