编写程序展示一个学校院系结构: 需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系
【传统方式】
将学院看做是学校的子类,系是学院的子类,小的组织继承大的组织
分析: 在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现管理操作,比如对学院、系的添加,删除,遍历
【组合模式】
把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作
组合模式解决这样的问题,当我们的要处理的对象可以生成一个树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
Leaf(树叶)
:表示“内容”的角色,里面不能放人其他对象,即没有孩子,其定义组合内元素的行为Composite(复合物)
:表示容器的角色,可以在其中放入Leaf和Composite,有一些对子部件的相关操作(如增加、删除),可能不具有叶子的某种行为Component
:使Leaf和Composite具有一致性的角色,Composite是 Leaf和Composite的父类。Compnet是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component 可以是抽象类或者接口Client
:使用Composite模式的角色【Component:组织】
package com.test.composite;
/**
* 组织,如论是系、学院还是学校,都属于组织
*/
public abstract class OrganizationComponent {
/**
* 名字
*/
private String name;
/**
* 说明
*/
private String des;
/**
* 为什么需要默认实现,而不是写成抽象方法呢?
* 因为叶子节点不需要实现add方法,如果是抽象方法的话,就要实现了,有点多余
* @param organizationComponent
*/
protected void add(OrganizationComponent organizationComponent) {
//默认实现,抛出不支持操作异常
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
/**
* 构造器
* @param name
* @param des
*/
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
/**
* 打印方法, 做成抽象的, 子类都需要实现
*/
protected abstract void print();
}
【Composite:大学】
package com.test.composite;
import java.util.ArrayList;
import java.util.List;
/**
* University 就是 Composite角色 , 可以管理College
*/
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
/**
* 构造器
* @param name
* @param des
*/
public University(String name, String des) {
super(name, des);
}
/**
* 重写add
* @param organizationComponent
*/
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
/**
* 重写remove
* @param organizationComponent
*/
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
/**
* print方法,就是输出 University 包含的学院
*/
@Override
protected void print() {
// 先输出学校的名字
System.out.println("--------------" + getName() + "--------------");
// 遍历 organizationComponents,其实就是遍历出学校的学院
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
【Composite:学院】
package com.test.composite;
import java.util.ArrayList;
import java.util.List;
public class College extends OrganizationComponent {
/**
* 存储系
*/
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
/**
* 构造器
* @param name
* @param des
*/
public College(String name, String des) {
super(name, des);
}
@Override
protected void add(OrganizationComponent organizationComponent) {
// 将来实际业务中,Colleage 的 add 和 University add 不一定完全一样
organizationComponents.add(organizationComponent);
}
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
/**
* print方法,就是输出学院包含的系
*/
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
// 遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
【Composite:系】
package com.test.composite;
public class Department extends OrganizationComponent {
//没有子节点,所以不用声明集合
public Department(String name, String des) {
super(name, des);
}
//add , remove 就不用写了,因为他是叶子节点
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
// 没有子节点,不需要输入其他东西
System.out.println(getName());
}
}
【Client】
package com.test.composite;
public class Client {
public static void main(String[] args) {
//创建大学
OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");
//创建大学的各个学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineerCollege = new College("信息工程学院", " 信息工程学院 ");
//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
infoEngineerCollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineerCollege.add(new Department("信息工程", " 信息工程好学 "));
//将学院加入到 学校
university.add(computerCollege);
university.add(infoEngineerCollege);
//输出大学的各个组织
university.print();
}
}
【运行】
--------------清华大学--------------
--------------计算机学院--------------
软件工程
网络工程
计算机科学与技术
--------------信息工程学院--------------
通信工程
信息工程
Process finished with exit code 0
【只打印某个学院的组织结构】
computerCollege.print();
【运行】
--------------计算机学院--------------
软件工程
网络工程
计算机科学与技术
Process finished with exit code 0
【Component:Entry类】
package com.test.composite.Sample;
/**
* 目录条目类 用来实现 File类 和 Directory类 的一致性
*/
public abstract class Entry {
/**
* 获取名字
*
* @return
*/
public abstract String getName();
/**
* 获取大小
*
* @return
*/
public abstract int getSize();
/**
* 加入目录条目,向文件夹中放入文件或者文件夹(Directory类来具体实现)
*
* @param entry
* @return
* @throws FileTreatmentException
*/
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
/**
* 为一览加上前缀并显示目录条目一览
*/
public void printList() {
printList("");
}
/**
* 为一览加上前缀
* protected修饰:只能被子类调用
* @param prefix
*/
protected abstract void printList(String prefix);
/**
* 显示代表类的文字
*
* @return
*/
public String toString() {
// 将文件名和文件大小一起显示出来
return getName() + " (" + getSize() + ")";
}
}
方法的默认实现是抛异常(一般都是这样做),这样如果子类没有重写该方法的话,就会抛异常
【Composite:文件类】
package com.test.composite.Sample;
/**
* 文件类
*/
public class File extends Entry {
private String name;
private int size;
/**
* 构造方法 创建文件
*
* @param name
* @param size
*/
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
protected void printList(String prefix) {
// 直接写this,会自动调用该类的toString()方法的
System.out.println(prefix + "/" + this);
}
}
【Composite:目录类】
package com.test.composite.Sample;
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends Entry {
/**
* 文件夹的名字
*/
private String name;
/**
* 文件夹中目录条目的集合
*/
private ArrayList directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
/**
* 获取大小:计算子文件或文件夹的大小总和
* @return
*/
@Override
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
// 无论子条目是文件夹还是文件,都可以直接调用其getSize()方法,这就是“容器与内容一致性”的好处
// 如果entry是目录,就会形成递归调用
size += entry.getSize();
}
return size;
}
/**
* 增加目录条目
* @param entry
* @return
*/
@Override
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
/**
* 显示目录条目一览
* @param prefix
*/
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
// 也是递归调用
entry.printList(prefix + "/" + name);
}
}
}
【自定义异常类】
package com.test.composite.Sample;
/**
* 自定义异常类
*/
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
【主类】
package com.test.composite.Sample;
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
【运行】
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)
Process finished with exit code 0
如何通过修改或者补充上面的代码来增加一个为 文件/目录
获取完整路径的功能,如/root/usr/yuki/Composite.java
【Component】
添加一个记录父条目的变量,和一个公共方法,该方法不需要子类去重写,因为实现逻辑都一样,如果不一样的话,就需要写成抽象方法
package com.test.composite.A1;
public abstract class Entry {
protected Entry parent;
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public void printList() {
printList("");
}
protected abstract void printList(String prefix);
public String toString() {
return getName() + " (" + getSize() + ")";
}
/**
* 获取条目的完整路径
*
* @return
*/
public String getFullName() {
StringBuffer fullname = new StringBuffer();
Entry entry = this;
do {
//需要将父条目的名字插到前面,而不是append到后面
fullname.insert(0, "/" + entry.getName());
entry = entry.parent;
} while (entry != null);
return fullname.toString();
}
}
【Composite:目录类】
当给目录加入元素时,需要指定元素的父元素,使用entry.parent = this;
来实现
package com.test.composite.A1;
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends Entry {
private String name;
private ArrayList directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
directory.add(entry);
entry.parent = this;
return this;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.printList(prefix + "/" + name);
}
}
}
处理的对象具有树形结构时
,非常适合使用组合模式如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式