结构型模式 ————顺口溜:适装桥组享代外
目录
1、组合模式(Composite Pattern)
又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。——它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
- 意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
- 何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
- 如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
- 关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
1.1 组合模式UML图
组合模式分为透明式的组合模式和安全式的组合模式:
(1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
((2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
图一 透明方式
图二 安全方式
1.2 日常生活中看组合模式
- 组合模式常用于和树状结构有关的场景中,其他场景使用的还是比较少的。
- 总部与分公司之间的树形关系。
1.3 应用实例
- 算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。
- 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
1.4 Java代码实现
1.4.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));
}
}
1.4.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);
}
}
2、组合模式在源码中的应用
2.1 JDK源码中组合模式体现
HashMap中有一个putAll方法,参数是一个Map,这就是一种组合模式的体现:
另外还有ArrayList中的addAll方法也是一样。
2.2 MyBatis源码中组合模式体现
MyBatis中有一个SqlNode接口,下面很多一级标签:
然后一级标签下面又有二级标签(这就是组合模式的体现):
3、组合模式的优缺点
3.1 优点
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 简化客户端代码
- 符合开闭原则
3.2 缺点
- 限制类型时较为复杂
- 使设计变得更加抽象
- 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
3.3 使用场景
- 希望客户端可以忽略组合对象与单个对象的差异时
- 处理一个树型结构时
- 文件、文件夹的管理
3.4 注意事项
注意事项:定义时为具体类。
4、组合模式与装饰器模式
装饰者模式、组合模式对比:
1、结构型模式——组合模式,用于把众多子对象组织为一个整体,及此程序员与大批对象打交道时可以将他们当作一个对象来对待,并将它们组织为层次性的树。通常它并不修改方法调用,
而只是将其沿组合对象与子对象的链向下传递,直到到达并落实在叶对象上。
2、结构型模式——装饰器模式,但它并非用于组织对象,而是用于在不修改现有对象或从其派生子类的前提下为其增添职责。在一些较简单的例子中,装饰者模式会透明而不加修改地传递所有方法调用,
不过,创建装饰者模式的目的就在于对方法进行修改。
尽管简单的组合对象与简单的装饰者对象是相同的,但二者却有着不同的焦点。组合对象并不修改方法调用,其着眼在点于组织子对象。而装饰者模式存在的唯一目的就是修改方法调用而不是组织子对象,因为子对象只有一个。
参考文章:
https://blog.csdn.net/atu1111/article/details/105506191
https://www.runoob.com/design-pattern/composite-pattern.html
https://blog.csdn.net/zwx900102/article/details/108554255