设计模型之组合模式
1. 组合模式
1.1 定义与特点
有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式的主要优点有:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
其主要缺点是:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
1.2 模式的结构
组合模式包含以下主要角色:
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
- 树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含
Add()、Remove()、GetChild() 等方法。
组合模式分为透明式的组合模式和安全式的组合模式:
(1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
((2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
1.3 问题由来
客户端面临处理一个树形结构时,并希望可以忽略组合对象和单个对象的差异时,比如文件夹和文件,就是一个树状结构,文件夹是组合对象,文件时单个对象。在比如生活中的公司就是一个树形结构,有部门这个子节点,员工这个子节点,部门是组合对象,员工是单个对象。
1.4 解决思路
如果想让单个对象和组合对象的使用具有一致性,其实就是我们可以通过协议(编程语言中的抽象类或接口)来定义对象的属性和行为,这样就实现了单个对象和组合对象的使用具有一致性。但是实际开发中,一般都不要一致性,比如文件夹和文件,文件夹有添加文件夹和文件的功能,但是文件没有添加功能,文件夹和文件不需要一致性,所以实际开发中对树状结构的处理大多采用的是安全式的组合模式。
1.5 UML类图
1.6 解决方案
1.透明方式
/**
* @author tbb
* 功能抽象
*/
public abstract class Component
{
private String name;
abstract void addChild(Component component);
abstract void removeChild(Component component);
abstract Component getChild(int i);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class File extends Component
{
@Override
public void addChild(Component component) {
// TODO Auto-generated method stub
}
@Override
public void removeChild(Component component) {
// TODO Auto-generated method stub
}
@Override
public Component getChild(int i) {
return null;
}
public File(String name) {
super.setName(name);
}
@Override
public String toString() {
return super.getName();
}
}
public class Folder extends Component
{
private List<Component> componentList = new ArrayList<Component>();
@Override
public void addChild(Component component)
{
this.componentList.add(component);
}
@Override
public void removeChild(Component component)
{
this.componentList.remove(component);
}
@Override
public Component getChild(int i) {
return this.componentList.get(i);
}
public List<Component> getComponentList() {
return componentList;
}
public void setComponentList(List<Component> componentList) {
this.componentList = componentList;
}
public Folder(String name) {
super.setName(name);
}
@Override
public String toString() {
return super.getName();
}
}
public class Test
{
public static void main(String[] args)
{
Folder dFolder = new Folder("D:");
Folder aFolder = new Folder("a文件夹");
dFolder.addChild(aFolder);
Folder bFolder = new Folder("b文件夹");
aFolder.addChild(bFolder);
File testFile = new File("test.txt");
bFolder.addChild(testFile);
System.out.println(dFolder.getChild(0).getChild(0).getChild(0));
}
}
2.安全方式
/**
* @author tbb
* 文件夹和文件公有部分类
*/
public class FileComponent
{
private String id;
private String name;
private String parentId;
private Date createTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
/**
* @author tbb
* 文件信息
*/
public class FileInfo extends FileComponent
{
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
/**
* @author tbb
* 文件夹信息
*/
public class FolderInfo extends FileComponent
{
}
/**
* @author tbb
* 返回给业务层文件夹信息类
*/
public class FolderInfoVO extends FolderInfo
{
/**
* 子文件夹节点
*/
private List<FolderInfoVO> childFolderNodes = new ArrayList<FolderInfoVO>();
/**
* 子文件节点
*/
private List<FileInfo> childFileList = new ArrayList<FileInfo>();
public List<FolderInfoVO> getChildFolderNodes() {
return childFolderNodes;
}
public void setChildFolderNodes(List<FolderInfoVO> childFolderNodes) {
this.childFolderNodes = childFolderNodes;
}
public List<FileInfo> getChildFileList() {
return childFileList;
}
public void setChildFileList(List<FileInfo> childFileList) {
this.childFileList = childFileList;
}
}
public class Test
{
private List<FolderInfo> folderInfoComponentList = new ArrayList<FolderInfo>();
private List<FileInfo> fileComponentList = new ArrayList<FileInfo>();
public FolderInfo addFolderInfo(FolderInfo folderInfo)
{
folderInfo.setId(UUID.randomUUID().toString());
folderInfo.setCreateTime(new Date());
folderInfoComponentList.add(folderInfo);
return folderInfo;
}
public FileInfo addFileInfo(FileInfo fileInfo)
{
fileInfo.setId(UUID.randomUUID().toString());
fileInfo.setCreateTime(new Date());
fileComponentList.add(fileInfo);
return fileInfo;
}
public List<FileInfo> getFileInfoListByFolderId(String id)
{
return fileComponentList.stream().filter(e->id.equals(e.getParentId())).collect(Collectors.toList());
}
public List<FolderInfoVO> getNode()
{
List<FolderInfo> folderInfoComponentList = this.folderInfoComponentList;
if (folderInfoComponentList.size() == 0 || folderInfoComponentList == null)
{
return null;
}
List<FolderInfoVO> resultFolderList = new ArrayList<FolderInfoVO>(); // 树状结构
Map<String, List<FolderInfoVO>> folderMap = new HashMap<String, List<FolderInfoVO>>();// 父id和同一个父id下的所有直接后代(子文件夹 和子文件夹下的文件)
for (FolderInfo folderInfo : folderInfoComponentList)
{
List<FileInfo> fileInfoList = getFileInfoListByFolderId(folderInfo.getId());
FolderInfoVO folderInfoVO = new FolderInfoVO();
BeanUtils.copyProperties(folderInfo, folderInfoVO);
folderInfoVO.setChildFileList(fileInfoList);
if (folderInfo.getParentId() != null)
{
List<FolderInfoVO> folderList = folderMap.get(folderInfo.getParentId());// 为了将同一个父id的文件夹 放入同一个list中
if (folderList == null)
{
folderList = new ArrayList<FolderInfoVO>();
}
folderList.add(folderInfoVO);
folderMap.put(folderInfo.getParentId(), folderList);
}
else
{
resultFolderList.add(folderInfoVO);
}
}
//resultFolderList目前只存放了所有 最顶层的文件夹及其文件
for (FolderInfoVO folderInfoVO : resultFolderList)
{
initRoot( folderInfoVO, folderMap);
}
return resultFolderList;
}
/**
* 将Map<String, List<FolderInfoVO>>对象中每一层级的文件夹list,放入直属父类的文件夹list对象属性中
* @param folderInfoVO
* @param folderMap
*/
public void initRoot(FolderInfoVO folderInfoVO, Map<String, List<FolderInfoVO>> folderMap)
{
List<FolderInfoVO> childFolderNodes = folderMap.get(folderInfoVO.getId());
if (childFolderNodes != null)
{
folderInfoVO.setChildFolderNodes(childFolderNodes);
for (FolderInfoVO f : childFolderNodes)
{
initRoot(f, folderMap); // 下一层级
}
}
else
{
return; // 最底层级别了
}
}
public static void main(String[] args)
{
Test test = new Test();
FolderInfo dFolderInfo = new FolderInfo();
dFolderInfo.setName("D:");
dFolderInfo = test.addFolderInfo(dFolderInfo);
FolderInfo aFolderInfo = new FolderInfo();
aFolderInfo.setName("a文件夹");
aFolderInfo.setParentId(dFolderInfo.getId());
aFolderInfo = test.addFolderInfo(aFolderInfo);
FolderInfo bFolderInfo = new FolderInfo();
bFolderInfo.setName("b文件夹");
bFolderInfo.setParentId(aFolderInfo.getId());
bFolderInfo = test.addFolderInfo(bFolderInfo);
FileInfo txtFileInfo = new FileInfo();
txtFileInfo.setName("test.txt");
txtFileInfo.setParentId(bFolderInfo.getId());
txtFileInfo.setContent("nihao");
txtFileInfo = test.addFileInfo(txtFileInfo);
FolderInfo a1FolderInfo = new FolderInfo();
a1FolderInfo.setName("a1文件夹");
a1FolderInfo.setParentId(dFolderInfo.getId());
a1FolderInfo = test.addFolderInfo(a1FolderInfo);
FolderInfo b1FolderInfo = new FolderInfo();
b1FolderInfo.setName("b1文件夹");
b1FolderInfo.setParentId(a1FolderInfo.getId());
b1FolderInfo = test.addFolderInfo(b1FolderInfo);
FileInfo docFileInfo = new FileInfo();
docFileInfo.setName("hello.doc");
docFileInfo.setContent("word");
docFileInfo.setParentId(b1FolderInfo.getId());
docFileInfo = test.addFileInfo(docFileInfo);
List<FolderInfoVO> resultList = test.getNode();
System.out.println(resultList);
}
}