组合模式(又称“部分-整体”模式)是一种结构型设计模式。组合模式的思想是:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 角色构成:
Component : 组合中所有具体组件的抽象接口,用于规范组件的属性和行为,相当于树的节点(Node)。Composite :组合中每个具体的组件,相当于树的枝节点。Leaf :位于未端面的具体组件,没有子节点,相当于树的叶子节点。 使用场景: 1、你想表示对象的部分-整体层次结构,如树形菜单,文件、文件夹的管理。文件系统由文件和目录组成,每个文件里装有内容,而每个目录的内容可以有文件和目录,目录就相当于是由单个对象或组合对象组合而成,如果你想要描述的是这样的数据结构,那么你就可以使用组合模式。 2、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。 3、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。 4、你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 5、在现实生活中,存在很多“部分-整体”的关系。汽车与轮胎、发动机的关系。医院与科室、医生的关系。学校与学院、学生、老师的关系。这是我在菜鸟教程中学到的内容,也拿他们这种例子说明一下吧。 一个目录的普通实现:
import java.util.ArrayList; import java.util.List; /** * 目录节点 * 包含: * 1、目录名 * 2、下级文件列表 * 3、下级目录列表 * 4、新增文件方法 * 5、新增目录方法 * 6、显示下级内容方法 */ public class Noder { String nodeName;//目录名 //通过构造器为目录命名 public Noder(String nodeName){ this.nodeName = nodeName; } List<Noder> nodeList = new ArrayList<Noder>();//目录的下级目录列表 List<Filer> fileList = new ArrayList<Filer>();//目录的下级文件列表 //新增下级目录 public void addNoder(Noder noder){ nodeList.add(noder); } //新增文件 public void addFiler(Filer filer){ fileList.add(filer); } //显示下级目录及文件 public void display(){ for(Noder noder:nodeList){ System.out.println(noder.nodeName); noder.display();//递归显示目录列表 } for(Filer filer:fileList){ filer.display(); } } }文件节点
/** * 文件节点 * 文件节点是终节点,无下级节点 * 包含: * 1、文件名 * 2、文件显示方法 */ public class Filer { String fileName;//文件名 public Filer(String fileName){ this.fileName = fileName; } //文件显示方法 public void display(){ System.out.println(fileName); } }测试:
import java.io.File; public class Clienter { public static void createTree(Noder node){ File file = new File(node.nodeName); File[] f = file.listFiles(); for(File fi : f){ if(fi.isFile()){ Filer filer = new Filer(fi.getAbsolutePath()); node.addFiler(filer); } if(fi.isDirectory()){ Noder noder = new Noder(fi.getAbsolutePath()); node.addNoder(noder); createTree(noder);//使用递归生成树结构 } } } public static void main(String[] args) { Noder noder = new Noder("E://ceshi"); createTree(noder);//创建目录树形结构 noder.display();//显示目录及文件 } }E:\ceshi\目录1 E:\ceshi\目录1\目录3 E:\ceshi\目录1\文件2.txt E:\ceshi\目录2 E:\ceshi\目录2\文件3.txt E:\ceshi\文件1.txt
组合模式 从上面的代码中可以看出,我们分别定义了文件节点对象与目录节点对象,这是因为文件与目录之间的操作不同,文件没有下级节点,而目录可以有下级节点,但是我们能不能这么想:既然文件与目录都是可以作为一个节点的下级节点而存在,那么我们可不可以将二者抽象为一类对象,虽然二者的操作不同,但是我们可以在实现类的方法实现中具体定义,比如文件没有新增下级节点的方法,我们就可以在文件的这个方法中抛出一个异常,不做具体实现,而在目录中则具体实现新增操作。显示操作二者都有,可以各自实现。而且由于我们将文件与目录抽象为一个类型,那么结合多态我们可以进行如下实现:
/** * 将文件与目录统一看作是一类节点,做一个抽象类来定义这种节点,然后以其实现类来区分文件与目录,在实现类中分别定义各自的具体实现内容 */ public abstract class Node { protected String name;//名称 //构造器赋名 public Node(String name){ this.name = name; } //新增节点:文件节点无此方法,目录节点重写此方法 public void addNode(Node node) throws Exception{ throw new Exception("Invalid exception"); } //显示节点:文件与目录均实现此方法 abstract void display(); } /** * 实现文件节点 */ public class Filer extends Node { //通过构造器为文件节点命名 public Filer(String name) { super(name); } //显示文件节点 @Override public void display() { System.out.println(name); } } /** * 实现目录节点 */ public class Noder extends Node { List<Node> nodeList = new ArrayList<Node>();//内部节点列表(包括文件和下级目录) //通过构造器为当前目录节点赋名 public Noder(String name) { super(name); } //新增节点 public void addNode(Node node) throws Exception{ nodeList.add(node); } //递归循环显示下级节点 @Override void display() { System.out.println(name); for(Node node:nodeList){ node.display(); } } } //测试 public static void createTree(Node node) throws Exception{ File file = new File(node.name); File[] f = file.listFiles(); for(File fi : f){ if(fi.isFile()){ Filer filer = new Filer(fi.getAbsolutePath()); node.addNode(filer); } if(fi.isDirectory()){ Noder noder = new Noder(fi.getAbsolutePath()); node.addNode(noder); createTree(noder);//使用递归生成树结构 } } } public static void main(String[] args) { Node noder = new Noder("E://ceshi"); try { createTree(noder); } catch (Exception e) { e.printStackTrace(); } noder.display(); }这就是读文件的例子,当然还有一个更加容易理解的例子,请参考(转自:https://www.cnblogs.com/yimengyizhen/p/11161152.html (感谢前辈们的倾囊相授))。
下面总结一个组合模式的优缺点 优点:
高层模块(客户端)调用简单。组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;节点自由增加,更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;缺点:
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。设计较复杂,客户端需要花更多时间理清类之间的层次关系;不容易限制容器中的构件;不容易用继承的方法来增加构件的新功能;