组合模式
组合模式和面向对象中“组合关系”完全是两码事,这里的组合模式主要用于处理结构为树形的数据。对于组合模式其应用场景比较特殊,这种模式在实际的项目开发中并不常见。但是一旦数据呈现出树形结构,这种模式就能发挥出很大的作用,可以让代码变得简洁。
组合模式的应用一:目录树
对于组合模式的定义是这样描述的:将一组对象组织成树形结构,以表示一种“部分—整体”的层次结构。
我们可以通过举例来解释组合模式。假设有这样一个需求:请设计一个类,表示文件系统中的目录,并能够方便实现以下的功能:
-
动态添加、删除某个目录下的子目录或文件
-
统计指定目录下的文件个数
-
统计指定目录下文件的总大小
对于这个需求我们可以这样实现:
public class FileSystemNode{
private String path;
private boolean isFile;
private List<FileSystemNode> subNodes = new ArrayList<>();
public FileSystemNode(String path , boolean isFile){
this.path = path;
this.iSFile = isFile;
}
public int countNumOfFiles(){
if(isFile)
return 1;
int numFiles = 0;
for(FileSystemNode fileOrDir : subNodes)
numFiles += fileOrDir.countNumOfFiles();
return numOfFiles;
}
public long countSizeOfFiles(){
if(ifFile){
File file = new File(path);
if(!file.exists()) return 0;
return file.length();
}
long sizeOfFiles = 0;
for(FileSystemNode fileOrDir : subNodes)
sizeOfFiles += fileOrDir.countSizeOfFiles();
return sizeOfFiles;
}
public void addSubNode(FileSystemNode fileOrDir){
subNodes.add(fileOrDir);
}
public String getPath(){
return path;
}
public void removeSubNode(FileSystemNode fileOrDir){
int size = subNodes.size();
int i = 0;
for(; i < size; ++i)
if(subNodes.get(i).getPath().equalsIsIgnoreCase(fileOrDir.getPath())) break;
if(i<size)
subNodes.remove(i);
}
}
如果从单纯的能不能用来看,上面的代码已经是可以用了。但是,如果开发的是一个大系统,从扩展性(文件或目录可能对应着不同的操作)、业务建模(文件和目录在业务上是两个概念)和代码的可读性(文件和目录区分对待才能更加符合人们对业务的认知)的角度来说,最好是要将文件和目录区分开来,用File表示文件Directory表示文件夹。根据这个思路我们将代码进行重构;
//抽象基类
public abstract class FileSystemNode{
protected String path;
public FileSystemNode(String path){
this.path = path;
}
public abstract int coun
tNumOfFiles();
public abstract long countSizeOfFiles();
public String getPath(){
return path;
}
}
//文件类
public class File extends FileSystemNode{
public File(String path){
super(path);
}
@Override
public int countNumOfFiles(){
return 1;
}
@Override
public long countSizeOfFiles(){
File file = new File(path);
if(!file.exists()) return 0;
return file.length();
}
}
//文件夹类
public class Directory extends FileSystemNode{
private List<FileSystemNode> subNodes = new ArrayList<>();
public Directory(String path){
super(path);
}
@Override
public int countNumOfFiles(){
int numOfFile = 0;
for(FileSystemNode fileOrDir : subNodes)
numOfFile += fileOrDir.countNumOfFiles();
return numOfFile;
}
@Override
public long countSizeOfFiles(){
long sizeOfFile = 0;
for(FileSystemNode fileOrDir : subNodes)
sizeOfFile += fileOrDir.countSizeOfFiles();
return sizeOfFiles;
}
public void addSubNode(FileSystemNode fileOrDir){
subNodes.add(fileOrDir);
}
public void removeSubNode(FileSystemNode fileOrDir){
int size = subNodes.size();
int i = 0;
for(;i < size; ++i)
if(subNodes.get(i).getPath().equalsIngnoreCase(fileOrDir.getPath()))
break;
if(i<size) subNodes.remove(i);
}
}
实际上,组合模式与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中业务场景中的数据可以表示成树这种数据结构,业务需求可以通过树上的递归遍历算法实现。
组合模式的应用二:人力树
上面我们举了文件系统的例子,接下来我们再举一个人力树的例子。理解了这两个例子,就理解了组合模式。在实际的项目中,再遇到类似的例子,只要“依葫芦画瓢”就好了。
假设有一个OA系统,有员工和门两种类型的数据,部门可以包含部门和员工。要再内存中构建这个公式的组织结构图,并计算部门的工资成本。
很显然这两种数据类型可以抽象成一棵树,那么我们就可以使用组合设计模式。
具体的代码实现如下:
//人力资源抽象类
public abstract class HumanResource{
protected long id;
protected double salary;
public HumanResource(long id){
this.id = id;
}
public long getId(){
return id;
}
public abstract double calculateSalary();
}
//职工类
class Employee extends HumanResource{
public Employee(long id , double salary){
super(id);
this.salary = salary;
}
@Override
public double calculateSalary(){
return salary;
}
}
//部门类
public Department extends HumanResource{
private List<HumanResource> subNodes = new ArrayList<>();
public Department(long id){
super(id);
}
@Override
public double calculateSalary(){
double totalSalary = 0;
for(HumanResource hr : subNodes)
totalSalary += hr.calculateSalary();
this.salary = totalSalary;
return this.salary;
}
public void addSubNode(HumanResource hr){
subNodes.add(hr);
}
}