组合模式是八种结构型模式之一。
组合模式就是将一组对象组织成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端可以统一单个对象和组合对象的处理逻辑。
这里有三个重要概念:树形结构、部分 - 整体、统一单个对象(部分)和组合对象(整体)的处理逻辑
树形结构
组合模式其实是代表的是一种树形数据结构。
下面我们通过分析树形结构,一步步推演出组合模式。通过这种方式了解组合模式及它的作用。
什么是树形结构,如图:
用代码表示树的对象:
//===代码一===
//叶子
class Leaf {
private String name;
}
//树枝
class Branch {
private String name;
private List<Leaf> leafs;
private List<Branch> branchs;
}
//树干
class Trunk {
private String name;
private List<Leaf> leafs;
private List<Branch> branchs;
}
现在我们为树上的每个对象加上名字:
//===代码二===
//叶子
class Leaf {
private String name;
Leaf(String name) {
this.name = name;
}
}
//树枝
class Branch {
private String name;
private List<Leaf> leafs;
private List<Branch> branchs;
Branch(String name) {
this.name = name;
leafs = new ArrayList<>();
branchs= new ArrayList<>();
}
public void addLeaf(Leaf leaf) {
leafs.add(leaf);
}
public void addBranch(Branch branch) {
branchs.add(branch);
}
}
//树干
class Trunk {
private String name;
private List<Leaf> leafs;
private List<Branch> branchs;
Trunk(String name) {
this.name = name;
leafs = new ArrayList<>();
branchs= new ArrayList<>();
}
public void addLeaf(Leaf leaf) {
leafs.add(leaf);
}
public void addBranch(Branch branch) {
branchs.add(branch);
}
}
class Demo {
public static void main(String[] args) {
Branch bc = new Branch("树枝C");
bc.addLeaf(new Leaf("叶子F"))
Branch bb = new Branch("树枝B");
bb.addLeaf(new Leaf("叶子C"))
bb.addLeaf(new Leaf("叶子D"))
Branch ba = new Branch("树枝A");
ba.addLeaf(bb);
ba.addBranch(bc);
Trunk trunk = new Trunk("树干");
trunk.addLeaf(new Leaf("叶子A"))
trunk.addLeaf(new Leaf("叶子B"))
ba.addBranch(ba);
}
}
现在如果要找叶子或树枝的上级,会发现上级可能是树干也可能是树枝。比如叶子A的上级(树干)和叶子C的上级(树枝B),这个时候应该如何来改造Leaf和Branch。不管是通过图或代码二,可以发现Branch和Trunk是很相似的,我们可以通过它们共同点抽象为Node。
共同点:都会有下级(叶子或树枝);根据共同点改写代码二
//===代码三===
//叶子
class Leaf {
private String name;
Leaf(String name) {
this.name = name;
}
private Node parent;
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
abstract class Node {
private String name;
private List<Leaf> leafs;
private List<Node> branchs;
Node(String name) {
this.name = name;
leafs = new ArrayList<>();
branchs= new ArrayList<>();
}
public void addLeaf(Leaf leaf) {
leaf.setParent(this);
leafs.add(leaf);
}
public void addBranch(Branch branch) {
branch.setParent(this);
branchs.add(branch);
}
public String getName() {
return name;
}
}
//树枝
class Branch extends Node{
Branch(String name) {
super(name);
}
private Node parent;
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
//树干
class Trunk extends Node{
Trunk(String name) {
super(name);
}
}
class Demo {
public static void main(String[] args) {
Branch bc = new Branch("树枝C");
bc.addLeaf(new Leaf("叶子F"))
Branch bb = new Branch("树枝B");
Leaf lc = new Leaf("叶子C");
bb.addLeaf(lc)
bb.addLeaf(new Leaf("叶子D"))
Branch ba = new Branch("树枝A");
ba.addLeaf(bb);
ba.addBranch(bc);
Trunk trunk = new Trunk("树干");
Leaf la = new Leaf("叶子A");
trunk.addLeaf(la)
trunk.addLeaf(new Leaf("叶子B"))
ba.addBranch(ba);
//获取父级
System.out.println(la.getParent().getName());
System.out.println(lc.getParent().getName());
}
}
通过图或代码三,可以发现Branch和Trunk不同点是Trunk没有上级,Branch和Leaf不同点是Leaf没有下级。根据不同点,改造代码三。
//===代码四===
abstract class Node {
private String name;
//下级对象(树枝和叶子)集合
private List<Node> childNodes;
private Node parent;
private boolean isLeaf = false;
Node(String name) {
this.name = name;
childNodes = new ArrayList<>();
}
Node(String name, boolean isLeaf) {
this(name);
this.isLeaf = isLeaf;
}
//判断是不是叶子
public boolean isLeaf(){
//树枝也可能没有下级,所以不能用这种方式判断是不是叶子
//如果要用这种方式判断,组合模式为透明组合模式。后面会解决什么透明组合模式。
//return this.parent != null && this.childNodes.size()==0;
return this.isLeaf;
}
//判断是不是树干
public boolean isRoot(){
return this.parent == null;
}
public void addChildNode(Node childNode) {
if (isLeaf()) return;
childNode.setParent(this);
childNodes.add(childNode);
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public String getName() {
return name;
}
}
//叶子
class Leaf extends Node
Leaf(String name) {
super(name, true);
}
}
//树枝
class Branch extends Node{
Branch(String name) {
super(name);
}
}
//树干
class Trunk extends Node{
Trunk(String name) {
super(name);
}
}
class Demo {
public static void main(String[] args) {
Branch bc = new Branch("树枝C");
bc.addLeaf(new Leaf("叶子F"))
Branch bb = new Branch("树枝B");
Leaf lc = new Leaf("叶子C");
bb.addLeaf(lc)
bb.addLeaf(new Leaf("叶子D"))
Branch ba = new Branch("树枝A");
ba.addLeaf(bb);
ba.addBranch(bc);
Trunk trunk = new Trunk("树干");
Leaf la = new Leaf("叶子A");
trunk.addLeaf(la)
trunk.addLeaf(new Leaf("叶子B"))
ba.addBranch(ba);
//获取父级
System.out.println(la.getParent().getName());
System.out.println(lc.getParent().getName());
}
}
部分 - 整体
如何理解部分 - 整体的关系,如下图,树干下面有树枝,树枝下面又有树枝。通过树干就可以找出整棵树的对象,同样通过树枝可以找出整个树枝下面的所有对象,那么树干代表的就是整体,它下面的树枝代表的就是部分。如果把树干下的树枝A看做整体,那么树枝B和树枝C就是部分。同理可推上级代表的是整体,下级代表部分。
统一单个对象(部分)和组合对象(整体)的处理逻辑
统一单个对象(部分)和组合对象(整体)的处理逻辑,就是统一下级和上级的处理逻辑。怎么统一呢?比如:计算叶子的数量,上级和下级可以通过一样的方法来计算。
//===代码五===
abstract class Node {
private String name;
//下级对象(树枝和叶子)集合
private List<Node> childNodes;
private Node parent;
private boolean isLeaf = false;
Node(String name) {
this.name = name;
childNodes = new ArrayList<>();
}
Node(String name, boolean isLeaf) {
this(name);
this.isLeaf = isLeaf;
}
//判断是不是叶子
public boolean isLeaf(){
//树枝也可能没有下级,所以不能用这种方式判断是不是叶子
//return this.parent != null && this.childNodes.size()==0;
return this.isLeaf;
}
//判断是不是树干
public boolean isRoot(){
return this.parent == null;
}
//计算所有叶子数量
public int countNumOfLeaf(){
if (isLeaf()) return 1;
int num = 0;
for (Node node: this.childNodes) {
num += node.countNumOfLeaf();
}
return num;
}
public void addChildNode(Node childNode) {
childNode.setParent(this);
childNodes.add(childNode);
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public String getName() {
return name;
}
}
//叶子
class Leaf extends Node
Leaf(String name) {
super(name, true);
}
}
//树枝
class Branch extends Node{
Branch(String name) {
super(name);
}
}
//树干
class Trunk extends Node{
Trunk(String name) {
super(name);
}
}
class Demo {
public static void main(String[] args) {
Branch bc = new Branch("树枝C");
bc.addChildNode(new Leaf("叶子F"))
Branch bb = new Branch("树枝B");
Leaf lc = new Leaf("叶子C");
bb.addChildNode(lc)
bb.addChildNode(new Leaf("叶子D"))
Branch ba = new Branch("树枝A");
ba.addChildNode(bb);
ba.addChildNode(bc);
Trunk trunk = new Trunk("树干");
Leaf la = new Leaf("叶子A");
trunk.addChildNode(la)
trunk.addChildNode(new Leaf("叶子B"))
trunk.addChildNode(ba);
//计算所有叶子数量
System.out.println(trunk.countNumOfLeaf());
System.out.println(ba.countNumOfLeaf());
}
}
透明组合模式
如果客户端不需要知道树上的对象具体代表什么意义,比如上面例子Leaf代表、Branch代表树枝、Rrunk代表树干,只需要能判断树的根节点、节点、叶子节点,这种组合模式就是透明组合模式。
//===代码六===
class Node {
private String name;
//下级对象集合
private List<Node> childNodes;
private Node parent;
Node(String name) {
this.name = name;
childNodes = new ArrayList<>();
}
//判断是不是叶子
public boolean isLeaf(){
//只要没有下级就是叶子
return this.parent != null && this.childNodes.size()==0;
}
//判断是不是根节点
public boolean isRoot(){
return this.parent == null;
}
//计算所有叶子数量
public int countNumOfLeaf(){
if (isLeaf()) return 1;
int num = 0;
for (Node node: this.childNodes) {
num += node.countNumOfLeaf();
}
return num;
}
public void addChildNode(Node childNode) {
childNode.setParent(this);
childNodes.add(childNode);
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public String getName() {
return name;
}
}
class Demo {
public static void main(String[] args) {
Node bc = new Node("树枝C");
bc.addChildNode(new Leaf("叶子F"))
Node bb = new Node("树枝B");
Node lc = new Node("叶子C");
bb.addChildNode(lc)
bb.addChildNode(new Node("叶子D"))
Node ba = new Node("树枝A");
ba.addChildNode(bb);
ba.addChildNode(bc);
Node root = new Node("树干");
Node la = new Node("叶子A");
root.addChildNode(la)
root.addChildNode(new Node("叶子B"))
root.addChildNode(ba);
//计算所有叶子数量
System.out.println(root.countNumOfLeaf());
System.out.println(ba.countNumOfLeaf());
}
}
透明组合模式比非透明组合模式简单的多,但也失去了代表具体对象的特有的属性。透明组合模式如果要带具体对象的特有的属性,则除非是像js、python这种动态类型语言。
总结
组合模式是树形数据结构,是类似树的一种抽象。
组合模式经常运用在有上下级关系的业务中,比如:电脑系统中的文件夹和文件,公司、部门及员工的关系,角色与权限的关系,功能菜单,网页页面元素等。
透明组合模式比非透明组合模式简单的多,但也失去了代表具体对象的特有的属性。