组合模式(Composite Pattern)
组合模式定义:组合模式允许你将对象组合成树形结构来表现“整体/局部”层次结构,组合能让客户以一致的方式处理个别对象以及对象组合。
当涉及到如:菜单,子菜单之类的问题时,会自然而然的想到使用树形结构
类之间的关系确如上图所示,但是这种设计复用性和扩展性都很低:
1,所有的菜单都有各自的add、remove以及getChild实现,复用性很低
2,类型转换问题,菜单完全不知道其包含的子元素的具体类型,这是个大问题(List<Object>)
针对上述2个问题:
1,抽象出共同的父类,以实现一些基本方法的复用
2,隐藏菜单与菜单项之间的差异,在这点,我们可以借鉴装饰者模式,将菜单和菜单项都当做一个菜单,如此一来,共同的父类也出现了
组合模式基本组成:
组件 - Menu、SpecificMenu
叶子节点 - MenuItem
组件可以包含其他组件以及叶子节点
public abstract class Menu { public void add(){ throw new UnsupportedOperationException(); } public void remove(){ throw new UnsupportedOperationException(); } public Menu getChild(int i){ throw new UnsupportedOperationException(); } public void print(){ throw new UnsupportedOperationException(); } }
public class MenuItem extends Menu { private String name; public MenuItem(String name){ this.name = name; } public void print(){ System.out.println(name); } }
public class SpecificMenu extends Menu { private String name; private List<Menu> children = new ArrayList<Menu>(); public SpecificMenu(String name){ this.name = name; } public void add(Menu menu){ children.add(menu); } public void remove(Menu menu){ children.remove(menu); } public Menu getChild(int i){ return children.get(i); } public void print(){ Iterator<Menu> it = children.iterator(); while(it.hasNext()){ Menu tmp = it.next(); tmp.print(); } } }
public class Cilent { public static void main(String[] args) { MenuItem mocha = new MenuItem("mocha"); MenuItem espressos = new MenuItem("espressos"); SpecificMenu coffeeMenu = new SpecificMenu("coffeeMenu"); coffeeMenu.add(mocha); coffeeMenu.add(espressos); MenuItem rice = new MenuItem("rice"); SpecificMenu restaurantMenu = new SpecificMenu("restaurantMenu"); restaurantMenu.add(coffeeMenu); restaurantMenu.add(rice); SpecificMenu allMenu = new SpecificMenu("allMenu"); allMenu.add(restaurantMenu); allMenu.print(); } }
测试结果:
mocha espressos rice
通常情况下,一个类具有单一的责任才是好的设计,但是这里,Menu既要扮演菜单,又需扮演菜单项
在组合模式中,通过牺牲了单一责任的设计原则,换取了透明性(对于一个节点,调用方无法确定其是组合还是节点,即组合与节点对用户来说是透明的)
为了保持透明性,组合内所有的对象都必须实现相同的接口,否则将必须考虑哪个对象使用哪个接口,这就失去了组合模式的意义
当然实现相同的接口也意味着有些对象包含一些没有意义的方法调用(如MenuItem通过继承依然拥有add、remove以及getChild方法,但是对叶子节点MenuItem来说,操作子节点的方法似乎是没有意义的,但是换个考虑方向,将MenuItem想象成没有子节点的节点,是不是感觉不一样了?)
可以看出这是一个折中的方案(折中方案很常见,比如为提高处理速度以“空间换时间”),由此也证明设计模式并不是完美无缺的,在使用前有必要仔细审视其带来的影响
PS:
1,什么时候使用组合模式:
有一些对象,它们之间具有“整体/部分”之间的关系,并且想用一致的方式对待整体与局部