组合模式允许用户将对象组合成树形结构来表现“整体/部分”的层次结构,从而能够以一致的方式处理单个对象以及对象组合。根据这个定义,首先能够想到的就是软件的菜单,一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项。
首先,不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件,其定义如下:
1 public abstract class MenuComponent { 2 public void add(MenuComponent menuComponent){ 3 throw new UnsupportedOperationException(); 4 } 5 6 public void remove(MenuComponent menuComponent){ 7 throw new UnsupportedOperationException(); 8 } 9 10 public MenuComponent getChild(int i){ 11 throw new UnsupportedOperationException(); 12 } 13 14 public String getName(){ 15 throw new UnsupportedOperationException(); 16 } 17 18 public String getDescription(){ 19 throw new UnsupportedOperationException(); 20 } 21 22 public void print(){ 23 throw new UnsupportedOperationException(); 24 } 25 }
这里选择抽象类来实现MenuComponent,是因为需要对一些方法给出默认实现,如此一来,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
接下来定义菜单类Menu:
1 public class Menu extends MenuComponent{ 2 private List<MenuComponent> menuComponentList; 3 private String name; 4 private String descrition; 5 6 public Menu(String name, String description){ 7 this.name = name; 8 this.descrition = description; 9 menuComponentList = new ArrayList<>(); 10 } 11 12 @Override 13 public void add(MenuComponent menuComponent){ 14 menuComponentList.add(menuComponent); 15 } 16 17 @Override 18 public void remove(MenuComponent menuComponent){ 19 menuComponentList.remove(menuComponent); 20 } 21 22 @Override 23 public MenuComponent getChild(int i){ 24 return menuComponentList.get(i); 25 } 26 27 @Override 28 public String getName() { 29 return name; 30 } 31 32 @Override 33 public String getDescription(){ 34 return descrition; 35 } 36 37 @Override 38 public void print(){ 39 System.out.println(getName() + ", " + getDescription()); 40 Iterator iterator = menuComponentList.iterator(); 41 while(iterator.hasNext()){ 42 MenuComponent menuComponent = (MenuComponent) iterator.next(); 43 menuComponent.print(); 44 } 45 } 46 }
Menu类应该覆盖自己感兴趣的方法,实际上这里它覆盖了父类的所有方法,这样做的原因仅仅是因为抽象类中的所有方法都是该类需要的,假设某一天我们在MenuComponent里面增加了color()方法,该方法只针对菜单项显示灰色底色,那么Menu累就不应该覆盖color()方法了。
让我们接着来实现MenuItem:
1 public class MenuItem extends MenuComponent{ 2 private String name; 3 private String descrition; 4 5 public MenuItem(String name, String descrition){ 6 this.name = name; 7 this.descrition = descrition; 8 } 9 10 @Override 11 public String getName() { 12 return name; 13 } 14 15 @Override 16 public String getDescription(){ 17 return descrition; 18 } 19 20 @Override 21 public void print(){ 22 System.out.println(getName() + ", " + getDescription()); 23 } 24 }
MenuItem只覆盖了getName()、getDescription()、print()方法,因为其他的方法对该类并不适用。
现在可以写个测试类看一下组合模式在菜单上面的表现了:
1 public class MenuComponentTest { 2 public static void main(String[] args){ 3 MenuComponentTest test = new MenuComponentTest(); 4 MenuComponent allMenu = test.createMenu(); 5 allMenu.print(); 6 } 7 8 public MenuComponent createMenu(){ 9 MenuComponent fileMenu = new Menu("文件", "文件相关选项"); 10 fileMenu.add(new MenuItem("设置", "可以更改一些设置项")); 11 fileMenu.add(new MenuItem("保存", "保存所有文件")); 12 13 MenuComponent saveAsMenu = new Menu("另存为", "另存为其他一些格式"); 14 saveAsMenu.add(new MenuItem("PDF", "另存为PDF格式")); 15 saveAsMenu.add(new MenuItem("docx","另存为docx格式")); 16 fileMenu.add(saveAsMenu); 17 18 MenuComponent helpMenu = new Menu("帮助","一些辅助项"); 19 helpMenu.add(new MenuItem("关于我们","软件制作方的一些消息")); 20 helpMenu.add(new MenuItem("帮助中心","电话和邮箱咨询")); 21 22 MenuComponent allMenu = new Menu("所有菜单","包含所有菜单的菜单"); 23 allMenu.add(fileMenu); 24 allMenu.add(helpMenu); 25 26 return allMenu; 27 } 28 }
打印结果为(没有缩进所以稍微丑了一点):
所有菜单, 包含所有菜单的菜单
文件, 文件相关选项
设置, 可以更改一些设置项
保存, 保存所有文件
另存为, 另存为其他一些格式
PDF, 另存为PDF格式
docx, 另存为docx格式
帮助, 一些辅助项
关于我们, 软件制作方的一些消息
帮助中心, 电话和邮箱咨询
最后回顾一下这个菜单的例子,类之间的关系如下: