我们知道古代的皇帝想要管理国家,是不可能直接管理到具体的每一个百姓的,因此设置了很多的机构,比如说三省六部,这些机构下边又会有很多的小组织,他们共同的管理这个国家。
再比如现在的公司, 下面有很多的部门,每个部门下有会分组。
在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。
组合模式的定义
组合(Composite
)模式的定义
:
有时又叫作部分-整体
模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点),用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性,属于结构型模式
。
组合模式的结构
组合模式包含以下主要角色:
抽象构件(Component
):
它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
树枝节点(Composite
):
是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
树叶节点(Leaf
):
是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
组合模式的实现
组合模式分为透明式
的组合模式和安全式
的组合模式。
透明式:
在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
/**
* 抽象构件:课程
*/
public abstract class CourseComponent {
//添加子节点的方法
public void addChild(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持添加操作");
}
//删除子节点的方法
public void removeChild(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持删除操作");
}
//获取课程名字
public String getName(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取名称操作");
}
//获取课程价格
public double getPrice(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取价格操作");
}
//打印信息
public void print(){
throw new UnsupportedOperationException("不支持打印操作");
}
}
/**
* 树枝节点:
*/
public class CoursePackage extends CourseComponent {
private List<CourseComponent> items = new ArrayList<CourseComponent>();//用于存储子节点
private String name;//课程名称
private Integer level;//目录级别
public CoursePackage(String name, Integer level) {
this.name = name;
this.level = level;
}
@Override
public void addChild(CourseComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public String getName(CourseComponent catalogComponent) {
return this.name;
}
@Override
public void removeChild(CourseComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public void print() {
System.out.println(this.name);
for(CourseComponent catalogComponent : items){
//控制显示格式
if(this.level != null){
for(int i = 0; i < this.level; i ++){
//打印空格控制格式
System.out.print(" ");
}
for(int i = 0; i < this.level; i ++){
//每一行开始打印一个+号
if(i == 0){ System.out.print(">"); }
System.out.print("-");
}
}
//打印标题
catalogComponent.print();
}
}
}
/**
* 树叶节点
*/
public class Course extends CourseComponent {
private String name;
private double price;
public Course(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName(CourseComponent catalogComponent) {
return this.name;
}
@Override
public double getPrice(CourseComponent catalogComponent) {
return this.price;
}
@Override
public void print() {
System.out.println(name + " (¥" + price + "元)");
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
System.out.println("============透明组合模式===========");
CourseComponent javaBase = new Course("Java入门课程",16800);//创建叶子节点
CourseComponent ai = new Course("人工智能",6800);//添创建叶子节点
CourseComponent packageCourse = new CoursePackage("Java架构师课程",2);//创建2级树枝节点
CourseComponent design = new Course("Java设计模式",3000);
CourseComponent source = new Course("源码分析",3000);
CourseComponent softSkill = new Course("软技能",3000);
//将上面的三个叶子节点插入到树枝节点
packageCourse.addChild(design);
packageCourse.addChild(source);
packageCourse.addChild(softSkill);
CourseComponent catalog = new CoursePackage("课程主目录",1);//创建1级树枝节点
catalog.addChild(javaBase);//添加叶子节点
catalog.addChild(ai);//添加叶子节点
catalog.addChild(packageCourse);//添加树枝节点
catalog.print();//打印该节点下的信息
}
}
程序运行结果如下:呈层级显示
============透明组合模式===========
课程主目录
>-Java入门课程 (¥16800.0元)
>-人工智能 (¥6800.0元)
>-Java架构师课程
>--Java设计模式 (¥3000.0元)
>--源码分析 (¥3000.0元)
>--软技能 (¥3000.0元)
安全式:
在该方式中,将管理叶子节点的方法移到树枝节点中,抽象构件和叶子节点没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道叶子节点和树枝节点的存在,所以失去了透明性。
/**
* 抽象构件:目录
*/
public abstract class Directory {
protected String name;
public Directory(String name) {
this.name = name;
}
public abstract void show();
}
/**
* 树枝节点:文件夹
*/
public class Folder extends Directory {
private List<Directory> dirs;//存放叶子节点
private Integer level;
public Folder(String name,Integer level) {
super(name);
this.level = level;
this.dirs = new ArrayList<Directory>();
}
@Override
public void show() {
System.out.println(this.name);
for (Directory dir : this.dirs) {
//控制显示格式
if(this.level != null){
for(int i = 0; i < this.level; i ++){
//打印空格控制格式
System.out.print(" ");
}
for(int i = 0; i < this.level; i ++){
//每一行开始打印一个+号
if(i == 0){ System.out.print(">"); }
System.out.print("-");
}
}
//打印名称
dir.show();
}
}
public boolean add(Directory dir) {
return this.dirs.add(dir);
}
public boolean remove(Directory dir) {
return this.dirs.remove(dir);
}
public Directory get(int index) {
return this.dirs.get(index);
}
public void list(){
for (Directory dir : this.dirs) {
System.out.println(dir.name);
}
}
}
/**
* 叶子节点:文件
*/
public class File extends Directory {
public File(String name) {
super(name);
}
@Override
public void show() {
System.out.println(this.name);
}
}
/**
* 测试类
*/
class Test {
public static void main(String[] args) {
System.out.println("============安全组合模式===========");
File qq = new File("QQ.exe");//叶子节点
File wx = new File("微信.exe");//叶子节点
Folder office = new Folder("办公软件",2);//2级树枝节点
File word = new File("Word.exe");//叶子节点
File ppt = new File("PowerPoint.exe");//叶子节点
File excel = new File("Excel.exe");//叶子节点
//将上边三个叶子节点插入2级树枝节点
office.add(word);
office.add(ppt);
office.add(excel);
Folder wps = new Folder("金山软件",3);//3级树枝节点
wps.add(new File("WPS.exe"));//将叶子节点插入3级树枝节点
office.add(wps); //将三级树枝节点插入2级树枝节点
Folder root = new Folder("根目录",1);//1级树枝节点
root.add(qq);
root.add(wx);
root.add(office);
System.out.println("----------show()方法展示所有层级目录-----------");
root.show();//展示所有层级目录
System.out.println("----------list()方法展示1级树枝节点-----------");
root.list();//展示1级树枝节点
}
}
程序运行结果如下:
============安全组合模式===========
----------show()方法展示所有层级目录-----------
根目录
>-QQ.exe
>-微信.exe
>-办公软件
>--Word.exe
>--PowerPoint.exe
>--Excel.exe
>--金山软件
>---WPS.exe
----------list()方法展示1级树枝节点-----------
QQ.exe
微信.exe
办公软件
组合模式的应用场景
1.在需要表示一个对象整体与部分的层次结构的场合。
2.要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
3.当子系统与其内各个对象层次呈现树形结构时(如,树形菜单,操作系统目录结构,公司组织架构等)
组合模式的优缺点
组合模式的主要优点
有:
1.清楚的定义分层次的复杂对象,表示对象的全部或部分层次
2.让客户端忽略层次的差异,方便对整个层次结构进行控制
3.简化客户端代码
4.符合开闭原则
其主要缺点
是:
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能;
4.使设计变得更抽象。