目录
1、通过代码体会组合模式
我们一定了解文件夹和文件的关系,文件夹可以包含多个文件,而文件不能再包含文件。组合模式相对比较简单,我们用一个课程目录结构的实现代码,体会组合模式。场景是课程包里包含子课程包和课程。比如是下面这样的一个层级结构。
技术进阶课程
Course name:Linux课程, price:11.0
Course name:Windows课程, price:9.0
Java课程目录
Course name:Java课程1, price:23.0
Course name:Java课程2, price:23.0
(1)结构图:
(2)代码
首先是通用的抽象类CatalogComponent,不管是课程包还是具体课程,都需要继承自它。但是继承的时候可以选择性的对抽象类的方法进行重写。我们知道,目录要能够包含文件,而文件不能再有子节点。
package structural.composite;
public abstract class CatalogComponent {
public void add(CatalogComponent catalogComponent) {
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(CatalogComponent catalogComponent) {
throw new UnsupportedOperationException("不支持删除操作");
}
public String getName(CatalogComponent catalogComponent) {
throw new UnsupportedOperationException("不支持获取名称操作");
}
public double getPrice(CatalogComponent catalogComponent) {
throw new UnsupportedOperationException("不支持获取价格操作");
}
public void print() {
throw new UnsupportedOperationException("不支持打印操作");
}
}
然后,课程包类:
package structural.composite;
import java.util.ArrayList;
import java.util.List;
public class CourseCatalog extends CatalogComponent{
private List<CatalogComponent> items=new ArrayList<CatalogComponent>();
private String name;
private Integer level;
public CourseCatalog(String name,Integer level) {
this.name=name;
this.level=level;
}
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public void print() {
System.out.println(this.name);
for (CatalogComponent catalogComponent : items) {
if (this.level!=null) {
for (int i = 0; i < level; i++) {
System.out.print(" ");
}
}
catalogComponent.print();
}
}
}
然后,是课程类:
package structural.composite;
public class Course extends CatalogComponent{
private String name;
private double price;
public Course(String name,double price) {
this.name=name;
this.price=price;
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public double getPrice(CatalogComponent catalogComponent) {
return this.price;
}
@Override
public void print() {
System.out.println("Course name:"+name+", price:"+price);
}
}
最后是测试类:
package structural.composite;
public class Test {
public static void main(String[] args) {
CatalogComponent linuxCourse=new Course("Linux课程", 11);
CatalogComponent windowsCourse=new Course("Windows课程", 9);
CatalogComponent javaCatalog = new CourseCatalog("Java课程目录",2);
CatalogComponent javaCourse1=new Course("Java课程1", 23);
CatalogComponent javaCourse2=new Course("Java课程2", 23);
javaCatalog.add(javaCourse1);
javaCatalog.add(javaCourse2);
CatalogComponent mainCatalog=new CourseCatalog("技术进阶课程",1);
mainCatalog.add(linuxCourse);
mainCatalog.add(windowsCourse);
mainCatalog.add(javaCatalog);
mainCatalog.print();
}
}
完整代码在这里:
2、理解与定义
(1)定义
通过以上的课程包与课程的关系,我们可以大概理解组合模式是个什么:
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。使客户端对单个对象和组合对象保持一致的方式处理。
(2)优点:
- 可以实现清楚的定义分层次的复杂对象,表示对象的全部或部分层次。
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制。从而简化了客户端代码。
- 符合开闭原则。当扩展叶子节点或容器节点,不会对原来的代码造成影响。
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
(3)缺点:
- 限制类型时会较复杂。比如上面的例子中我Java课程包里只想限制容纳Java类的课程,而其他类型课程不能出现在这个目录包下。这种需求在当前设计模式下就不好实现。
(4) 适用场景
在以下情况下可以考虑使用组合模式:
(1) 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
(2) 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
(3) 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
3、抽象组件的实现讨论(结构图)
(1)两种实现方式
方式一:
就像文章一开始的例子那样,我们在抽象类中定义容器节点和叶子节点所需的所有方法,由容器节点和叶子节点自己定义需要重写哪些方法。但是,这样就需要在抽象类中对定义的方法进行处理,防止客户端使用多态的方式调用到节点对象未定义的方法上,比如我们代码中默认方法都是抛出异常。
方式二:
只在抽象类中定义 容器节点和叶子节点通用的方法,节点操作的方法放在容器节点来定义实现。
4、源码学习
(1)java.awt.Container是典型的容器类,Component就是那个抽象类。
public class Container extends Component {
public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}
}
(2)ArrayList是典型的容器类,Collection就是那个抽象类。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
}
public interface List<E> extends Collection<E> {
}
(3)Mybatis中的MixedSqlNode是典型的容器对象,SqlNode是抽象的那个接口。
package org.apache.ibatis.scripting.xmltags;
import java.util.Iterator;
import java.util.List;
public class MixedSqlNode implements SqlNode {
private List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
public boolean apply(DynamicContext context) {
Iterator i$ = this.contents.iterator();
while(i$.hasNext()) {
SqlNode sqlNode = (SqlNode)i$.next();
sqlNode.apply(context);
}
return true;
}
}
参考: