Composite design pattern
组合模式的概念、组合模式的结构、组合模式的优缺点、组合模式的使用场景、组合模式的实现示例、组合模式的源码分析
1、组合模式的概念
组合模式,即部分整体模式,是用于把一组相似的对象当做一个单一个的对象。组合模式依据树形结构来组合对象,用以表示部分以及整体层次。这种类型的设计模式属于结构形模式,它创建了对象组的树形结构。
2、组合模式的结构
- 抽象根节点:定义组合中各层次对象的共有行为和属性,可以预先定义一些默认行为和属性。
- 子节点:继承自抽象根节点,实现其对应的具体行为,定义子节点的行为和属性。
- 叶子节点:继承自根节点,实现其对应的具体行为,定义叶子节点的行为和属性。
组合模式的分类:
-
透明组合模式:
透明组合模式,即抽象根节点角色中定义了所有用于管理成员对象的方法,比如上述类图中的 addNode、removeNode、getNode 等方法。这样做的好处是确保所有构件都有相同的行为,透明组合模式也是组合模式的标准实现。其缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,可能会出现叶子对象调用容器对象方法的运行错误。
-
安全组合模式:
安全组合模式,即抽象根节点角色中没有定义关于管理成员对象的任何方法,而是定义在了容器角色中,这样做的好处是避免了叶子对象调用容器方法而出现的运行错误;缺点是不够透明,客户端不能完全针对抽象编程,必须区别对待叶子构件和容器构件。
3、组合模式的优缺点
- 优点:
- 高层次模块调用简单。
- 节点自由增加,满足开闭原则。
- 缺点:
- 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
4、组合模式的使用场景
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致的对待它们。
- 组合模式正是应树形结构而生,所以在有树形结构的地方可食用。
- 当一个系统中能够分离出叶子对象和容器对象,且它们的类型不固定,需要增加一些新的类型时。
5、组合模式的实现示例
抽象根节点:
public abstract class MenuComponent {
protected String name;
protected Integer level;
public void addNode(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void removeNode(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getNode(int index) {
throw new UnsupportedOperationException();
}
public abstract void print();
public String getName() {
return this.name;
}
}
子节点:
public class Menu extends MenuComponent {
private List<MenuComponent> children;
public Menu(String name, Integer level) {
this.name = name;
this.level = level;
this.children = new ArrayList<>();
}
@Override
public void addNode(MenuComponent menuComponent) {
this.children.add(menuComponent);
}
@Override
public void removeNode(MenuComponent menuComponent) {
this.children.remove(menuComponent);
}
@Override
public MenuComponent getNode(int index) {
return this.children.get(index);
}
@Override
public void print() {
for (int i = 1; i < this.level; i++) {
System.out.print("--");
}
System.out.println(this.getName());
for (MenuComponent child : this.children) {
child.print();
}
}
}
叶子节点:
public class MenuItem extends MenuComponent {
public MenuItem(String name, Integer level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
for (int i = 1; i < this.level; i++) {
System.out.print("--");
}
System.out.println(this.getName());
}
}
测试:
public class CompositeTest {
public static void main(String[] args) {
MenuComponent menu = new Menu("系统管理", 1);
MenuComponent menu1 = new Menu("权限管理", 2);
MenuComponent menu2 = new Menu("日志管理", 2);
menu1.addNode(new MenuItem("权限管理", 3));
menu1.addNode(new MenuItem("角色管理", 3));
menu1.addNode(new MenuItem("用户管理", 3));
menu2.addNode(new MenuItem("日志监控", 3));
menu2.addNode(new MenuItem("告警管理", 3));
menu.addNode(menu1);
menu.addNode(menu2);
menu.print();
}
}
测试结果:
系统管理
--权限管理
----权限管理
----角色管理
----用户管理
--日志管理
----日志监控
----告警管理
6、组合模式的源码分析
java.util.map 中的 putAll() 方法的设计实际上使用了组合模式,其可以将 Map 接口的其它字实现类的对象实例放入当前对象自己维护的数据结构中。同理,List 接口中的 addAll() 也使用了组合模式。
public interface Map<K, V> {
void putAll(Map<? extends K, ? extends V> m);
}
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
}