设计模式之组合模式
概念
组合模式又叫做部分-整体模式,组合模式允许你将对象组合成树形结构来表现“部分-整体”的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。(客户程序可以像处理简单元素一样来处理复杂元素)从而使得客户端程序与复杂的内部结构解耦。
组合模式核心
(1)抽象构件角色:定义了叶子和容器构件的共同点
(2)叶子(Leaf)构件角色:无子节点
(3)容器(Composite)构件角色:有容器特征,可以包含子节点
类图
例子:杀毒软件
代码实例
使用接口构建
/**
* 抽象构建角色
* @author Administrator
*
*/
public interface AbstractFile {
void killVirus(); //杀毒
}
/**
* 叶子构件角色
* @author Administrator
*
*/
class ImageFileimplements AbstractFile {
private Stringname;
public ImageFile(Stringname) {
super();
this.name =name;
}
@Override
public void killVirus() {
System.out.println("---图像文件:"+name+",进行查杀!");
}
}
class TextFileimplements AbstractFile {
private Stringname;
public TextFile(Stringname) {
super();
this.name =name;
}
@Override
public void killVirus() {
System.out.println("---文本文件:"+name+",进行查杀!");
}
}
class VideoFileimplements AbstractFile {
private Stringname;
public VideoFile(Stringname) {
super();
this.name =name;
}
@Override
public void killVirus() {
System.out.println("---视频文件:"+name+",进行查杀!");
}
}
/**
* 容器构件角色
* @author Administrator
*
*/
class Folderimplements AbstractFile {
private Stringname;
//定义容器,用来存放本容器构建下的子节点
private List<AbstractFile>list =new ArrayList<AbstractFile>();
public Folder(Stringname) {
super();
this.name =name;
}
public void add(AbstractFilefile){
list.add(file);
}
public void remove(AbstractFilefile){
list.remove(file);
}
public AbstractFile getChild(int index){
return list.get(index);
}
@Override
public void killVirus() {
System.out.println("---文件夹:"+name+",进行查杀");
for (AbstractFilefile :list) {
file.killVirus();
}
}
}
测试:
public class Client {
public static void main(String[]args) {
//定义抽象构件
AbstractFile f2,f3,f4,f5;
//定义容器
Folder f1 =new Folder("我的收藏");
f2 = new ImageFile("老高的大头像.jpg");
f3 = new TextFile("Hello.txt");
f1.add(f2);
f1.add(f3);
Folder f11 =new Folder("电影");
f4 = new VideoFile("笑傲江湖.avi");
f5 = new VideoFile("神雕侠侣.avi");
f11.add(f4);
f11.add(f5);
f1.add(f11);
f1.killVirus();
}
}
结果:
使用抽象构建
/**
* 抽象构建角色
* @author mama
*
*/
public abstract class Component {
private Stringname;
public Component(Stringname) {
super();
this.name =name;
}
/**
* 共有方法
*/
public void getName() {
System.out.println(this.name);
}
}
/**
* 树叶构建类角色
* @author mama
*
*/
public class Leafextends Component{
public Leaf(Stringname) {
super(name);
}
}
/**
* 树枝节点类构建(容器)
*
* @author mama
*
*/
public class Compositeextends Component {
private LinkedList<Component>children;
public Composite(Stringname) {
super(name);
this.children =new LinkedList<Component>();
}
// 添加子节点
public void add(Componentchild) {
this.children.add(child);
}
// 删除子节点
public void remove(Stringchild) {
this.children.remove(child);
}
// 获取子树
public LinkedList<Component> getChildren() {
return this.children;
}
}
测试:
public class Client {
public static void main(String[]args) {
Composite root =new Composite("树根");
Composite branch01 =new Composite("树枝01");
Composite branch02 =new Composite("树枝02");
root.add(branch01);
root.add(branch02);
Component leaf01 =new Leaf("树叶01");
Component leaf02 =new Leaf("树叶02");
Component leaf03 =new Leaf("树叶03");
Component leaf04 =new Leaf("树叶04");
Component leaf05 =new Leaf("树叶05");
branch01.add(leaf01);
branch01.add(leaf02);
branch02.add(leaf03);
branch02.add(leaf04);
branch02.add(leaf05);
displayTree(root);
}
// 递归遍历整棵树,也可以把遍历写道树枝类中
public static void displayTree(Compositeroot) {
LinkedList<Component> children = root.getChildren();
for (Componentc :children) {
if (c instanceof Leaf) {
System.out.print("\t");
c.getName();
} else {
c.getName();
// 递归
displayTree((Composite)c);
}
}
}
}
结果:
其实,在实际应用中,并不是这样子手工地构建一棵复杂的树的,应该是我们已经将整棵树的节点内容、逻辑关系都存储在数据库表中,由于表中的各个节点记录都保存有自身的一些相关信息,包括是否为树叶、父节点等等,开发人员需要的就是让程序从数据库中的表中读取记录来构建整棵树。
此外,上面的代码中只能从根节点往下遍历,不能够从某一节点开始往上遍历,解决这个问题可以在抽象构件类 Component类中添加一个parent属性,再添加相应setParent() 、getParent()方法即可。而关于不同的遍历方法再具体实现一下就完成了。
上面的类图是属于安全模式的,因为 Leaf类不具有add 、remove 等方法,这些具体方法是被下置到Composite类(树枝节点类)中去具体实现了。
差别仅在于将 add 、remove 等方法上升到抽象构件类Component中去了。那么此时Leaf类在具体实现时就必须将继承而来的add、remove等不可用、不合逻辑的方法给注解 Deprecated掉,并抛出适当的异常,不提供给用户使用。其实,这种模式下使得我们在遍历整棵树的时候可以不进行强制类型转换。看看上面的 displayTree()方法,里面在使用递归遍历时就使用到了 (Composite)c强制类型转换了。
代码如下
/**
* 抽象构建
* @author mama
*
*/
public abstract class Component {
protected Stringname;
public Component(Stringname)
{
this.name =name;
}
protected abstract void add(Componentcomponent);
protected abstract void remove(Componentcomponent);
protected abstract void operation(int depth);
protected abstract List<Component> getChildren();
}
/**
* 树叶构建
* @author mama
*
*/
public class Leafextends Component
{
public Leaf(Stringname)
{
super(name);
}
@Deprecated
public void add(Componentcomponent)throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
@Deprecated
public void remove(Componentcomponent)throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
@Override
protected void operation(int depth){
String temp = "";
for(int i=0;i<depth;i++)
{
temp +=" ";
}
System.out.println(temp+this.name);
}
@Deprecated
protected List<Component>getChildren()throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* 树枝构建(容器)
* @author mama
*
*/
public class Compositeextends Component
{
private LinkedList<Component>children;
public Composite(Stringname)
{
super(name);
this.children =new LinkedList<>();
}
public void add(Componentcomponent)
{
this.children.add(component);
}
@Override
public void remove(Componentcomponent)
{
this.children.remove(component);
}
@Override
public LinkedList<Component> getChildren()
{
return children;
}
@Override
protected void operation(int depth)
{
String temp = "";
for(int i=0;i<depth;i++)
{
temp +=" ";
}
LinkedList<Component> children = this.getChildren();
System.out.println(temp+this.name);
for (Componentc :children) {
c.operation(depth+1);
}
}
}
测试
public class Client {
public static void main(String[]args)
{
Composite root = new Composite("树根");
Composite branch01 = new Composite("树枝01");
Composite branch02 = new Composite("树枝02");
Composite branch03 = new Composite("树枝03");
Composite branch04 = new Composite("树枝04");
branch01.add(new Leaf("树叶01"));
branch01.add(new Leaf("树叶02"));
branch03.add(new Leaf("树叶03"));
branch03.add(new Leaf("树叶04"));
branch03.add(new Leaf("树叶05"));
branch01.add(branch03);
branch02.add(new Leaf("树叶06"));
branch02.add(new Leaf("树叶07"));
branch02.add(new Leaf("树叶08"));
branch04.add(new Leaf("树叶09"));
branch04.add(new Leaf("树叶10"));
branch02.add(branch04);
root.add(branch01);
root.add(branch02);
root.operation(0);
}
}
结果:
组合模式工作流程分析:
(1)组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子
(2)当容器对象的指定方法被调用时,将遍历整个树形是结构,寻找也包含这个方法的成员,并调用执行,其中,使用了递归调用的机制对整个结构进行处理。
优点:
使客户端调用简单,客户端可以一直的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。更容易在组合体内加入对象不见,客户端不必因为加入了新的对象不见而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利。
缺点:
组合模式不容易限制组合中的构件。
使用场景
(1)用于对象的部分-整体层次结构,如树形菜单、文件夹菜单、部门组织架构等。
(2)对用户隐藏组合对象与单个对象的不同,使得用户统一地使用组合结构中的所有对象。
总结
(1)组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。
(2)如果你想要创建层次结构,并可以在其中以相同的方式对待所有元素,那么组合模式就是最理想的选择。
开发中应用的场景
(1)操作系统的资源管理器
(2)GUI中的容器层次图
(3)XML文件解析
(4)OA系统中,组织结构的处理
(5)Junit单元测试框架
底层设计就是典型的组合模式,TestCase(叶子),TestUnite(容器),Test接口(抽象)