Composite概述
与其他模式区别
复合模式和装饰器模式看上去也非常相似。复合模式中的组件会形成树状的嵌套层次关系,一个组件往往不会去修改另一个组件的输出,而是聚合每个组件的功能,或聚合每个组件的输出得到一个最终的输出,复合模式经常用来开发表达式组件,例如Where,And,Or, Not这种可以灵活嵌套的复合表达式。而装饰器模式是一种链式的嵌套关系,一个装饰器往往基于被装饰的组件的输出,每一个装饰器处理一部分功能,再交给下一个装饰器继续处理。
玩具代码案例 - 文件过滤器
复合组件
ICompositeFilter
package online.javabook.gof.structural.patterns3.composite.file.filter.composite;
import online.javabook.gof.structural.patterns3.composite.file.filter.IFilter;
public interface ICompositeFilter extends IFilter {
public void addFilter(IFilter filter);
public void delFilter(IFilter filter);
}
AndCompositeFilter(与复合过滤器)
package online.javabook.gof.structural.patterns3.composite.file.filter.composite;
import online.javabook.gof.structural.patterns3.composite.file.filter.IFilter;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class AndCompositeFilter implements ICompositeFilter {
private List<IFilter> andFilters = new ArrayList<>();
@Override
public boolean accept(File file) {
for (IFilter filter : andFilters){
boolean isTrue = filter.accept(file);
if(!isTrue) return false;
}
return true;
}
@Override
public void addFilter(IFilter filter) {
andFilters.add(filter);
}
@Override
public void delFilter(IFilter filter) {
andFilters.remove(filter);
}
}
OrCompositeFilter(或复合过滤器)
package online.javabook.gof.structural.patterns3.composite.file.filter.composite;
import online.javabook.gof.structural.patterns3.composite.file.filter.IFilter;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class OrCompositeFilter implements ICompositeFilter {
private List<IFilter> orFilters = new ArrayList<>();
@Override
public boolean accept(File file) {
for (IFilter filter : orFilters){
boolean isTrue = filter.accept(file);
if(isTrue) return true;
}
return false;
}
@Override
public void addFilter(IFilter filter) {
orFilters.add(filter);
}
@Override
public void delFilter(IFilter filter) {
orFilters.remove(filter);
}
}
NotFilter(取反过滤器)
package online.javabook.gof.structural.patterns3.composite.file.filter.composite;
import online.javabook.gof.structural.patterns3.composite.file.filter.IFilter;
import java.io.File;
public class NotFilter implements IFilter {
private IFilter filter;
public NotFilter(IFilter filter) {
this.filter = filter;
}
@Override
public boolean accept(File file) {
return !filter.accept(file);
}
}
可以横向扩展的嵌套子组件
IFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public interface IFilter {
boolean accept(File file);
}
CanExecuteFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class CanExecuteFilter implements IFilter {
@Override
public boolean accept(File file) {
return file.canExecute();
}
}
CanReadFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class CanReadFilter implements IFilter {
@Override
public boolean accept(File file) {
return file.canRead();
}
}
CanWriteFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class CanWriteFilter implements IFilter {
@Override
public boolean accept(File file) {
return file.canWrite();
}
}
HiddenFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class HiddenFilter implements IFilter {
@Override
public boolean accept(File file) {
return file.isHidden();
}
}
PrefixFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class PrefixFilter implements IFilter {
private String prefix;
public PrefixFilter(String prefix) {
this.prefix = prefix;
}
@Override
public boolean accept(File file) {
return file.getName().startsWith(prefix);
}
}
SizeFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class SizeFilter implements IFilter {
private int size;
public SizeFilter(int size) {
this.size = size;
}
@Override
public boolean accept(File file) {
return file.length() <= size;
}
}
SuffixFilter
package online.javabook.gof.structural.patterns3.composite.file.filter;
import java.io.File;
public class SuffixFilter implements IFilter {
private String suffix;
public SuffixFilter(String suffix) {
this.suffix = suffix;
}
@Override
public boolean accept(File file) {
return file.getName().endsWith(suffix);
}
}
不基于Composite模式的实现
许许多多的if…else除了不那么OOP,也不方便重构和扩展
Main
package online.javabook.gof.structural.patterns3.composite.file.app.bad;
import online.javabook.gof.structural.patterns3.composite.file.filter.*;
import java.io.File;
public class Main {
public static void main(String[] args) {
// ------------------------------------------------------------------
File file = new File("abc.txt.");
if( file.isHidden() != true ||
file.canExecute() == true ||
file.canWrite() == true ||
file.canRead() == true ||
file.length() <= 100 ||
file.getName().startsWith("a") ||
file.getName().endsWith("c")) {
System.out.println("I am abc.txt");
}
// ------------------------------------------------------------------
File file2 = new File("def.txt.");
if( file2.isHidden() != true
&&
(file2.canExecute() == true ||
file2.canWrite() == true ||
file2.canRead() == true )
&&
(file2.length() <= 100 ||
file2.getName().startsWith("a") ||
file2.getName().endsWith("c"))) {
System.out.println("I am def.txt");
}
// ------------------------------------------------------------------
File file3 = new File("hij.txt.");
if( new HiddenFilter().accept(file3)
&&
(new CanExecuteFilter().accept(file3) ||
new CanReadFilter().accept(file3) ||
new CanWriteFilter().accept(file3) )
&&
(new PrefixFilter("a").accept(file3) ||
new SuffixFilter("c").accept(file3) ||
new SizeFilter(100).accept(file3) )) {
System.out.println("I am def.txt");
}
}
}
Console
I am abc.txt
基于Composite模式的实现
Main
package online.javabook.gof.structural.patterns3.composite.file.app.good;
import online.javabook.gof.structural.patterns3.composite.file.filter.*;
import online.javabook.gof.structural.patterns3.composite.file.filter.composite.AndCompositeFilter;
import online.javabook.gof.structural.patterns3.composite.file.filter.composite.ICompositeFilter;
import online.javabook.gof.structural.patterns3.composite.file.filter.composite.OrCompositeFilter;
import java.io.File;
public class Main {
public static void main(String[] args) {
ICompositeFilter and = new AndCompositeFilter();
and.addFilter(new CanExecuteFilter());
and.addFilter(new CanReadFilter());
and.addFilter(new CanWriteFilter());
and.addFilter(new PrefixFilter("a"));
and.addFilter(new SuffixFilter("c"));
and.accept(new File("abc.txt"));
// ------------------------------------------------------------------
ICompositeFilter or = new OrCompositeFilter();
ICompositeFilter and1 = new AndCompositeFilter();
and1.addFilter(new CanExecuteFilter());
and1.addFilter(new CanReadFilter());
and1.addFilter(new CanWriteFilter());
ICompositeFilter and2 = new AndCompositeFilter();
and2.addFilter(new PrefixFilter("a"));
and2.addFilter(new SuffixFilter("c"));
or.addFilter(and1);
or.addFilter(and2);
// where (CanExecuteFilter and CanReadFilter and CanWriteFilter) or (PrefixFilter and SuffixFilter)
or.accept(new File("abc.txt"));
}
}
Console
I am abc.txt
现实世界中的合成模式
来自于Apache Commons-IO中真实的文件过滤器,其中位于过滤器上层的“与或非”合成器。合成模式和装饰器模式看上去很像,装饰器模式中的组件也是一种组合,但是装饰器之间的调用往往要求特定的顺序,就像管道一样,数据从一个装饰器组件处理完,进入管道中的下一个装饰器组件,比如输入输出流中,先要读取数据,再进行缓冲处理,而不能将这个顺序倒转。而组合模式中的组件大多是平等的,对顺序性没有太大的要求,仅仅是对一组功能的组合嵌套并执行,但是对外又是一个平平无奇的上层接口,而无需关心内部组合了那些组件。
FileFilter
@FunctionalInterface
public interface FileFilter {
/**
* Tests whether or not the specified abstract pathname should be
* included in a pathname list.
*
* @param pathname The abstract pathname to be tested
* @return <code>true</code> if and only if <code>pathname</code>
* should be included
*/
boolean accept(File pathname);
}
ConditionalFileFilter
public interface ConditionalFileFilter {
void addFileFilter(IOFileFilter var1);
List<IOFileFilter> getFileFilters();
boolean removeFileFilter(IOFileFilter var1);
void setFileFilters(List<IOFileFilter> var1);
}
AndFileFilter
public class AndFileFilter extends AbstractFileFilter implements ConditionalFileFilter, Serializable {
private static final long serialVersionUID = 7215974688563965257L;
private final List<IOFileFilter> fileFilters;
public AndFileFilter() {
this(0);
}
private AndFileFilter(ArrayList<IOFileFilter> initialList) {
this.fileFilters = (List)Objects.requireNonNull(initialList, "initialList");
}
private AndFileFilter(int initialCapacity) {
this(new ArrayList(initialCapacity));
}
public AndFileFilter(IOFileFilter filter1, IOFileFilter filter2) {
this(2);
this.addFileFilter(filter1);
this.addFileFilter(filter2);
}
public AndFileFilter(IOFileFilter... fileFilters) {
this(((IOFileFilter[])Objects.requireNonNull(fileFilters, "fileFilters")).length);
this.addFileFilter(fileFilters);
}
public AndFileFilter(List<IOFileFilter> fileFilters) {
this(new ArrayList((Collection)Objects.requireNonNull(fileFilters, "fileFilters")));
}
public boolean accept(File file) {
if (this.isEmpty()) {
return false;
} else {
Iterator var2 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var2.hasNext()) {
return true;
}
fileFilter = (IOFileFilter)var2.next();
} while(fileFilter.accept(file));
return false;
}
}
public boolean accept(File file, String name) {
if (this.isEmpty()) {
return false;
} else {
Iterator var3 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var3.hasNext()) {
return true;
}
fileFilter = (IOFileFilter)var3.next();
} while(fileFilter.accept(file, name));
return false;
}
}
public FileVisitResult accept(Path file, BasicFileAttributes attributes) {
if (this.isEmpty()) {
return FileVisitResult.TERMINATE;
} else {
Iterator var3 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var3.hasNext()) {
return FileVisitResult.CONTINUE;
}
fileFilter = (IOFileFilter)var3.next();
} while(fileFilter.accept(file, attributes) == FileVisitResult.CONTINUE);
return FileVisitResult.TERMINATE;
}
}
public void addFileFilter(IOFileFilter fileFilter) {
this.fileFilters.add(Objects.requireNonNull(fileFilter, "fileFilter"));
}
public void addFileFilter(IOFileFilter... fileFilters) {
IOFileFilter[] var2 = (IOFileFilter[])Objects.requireNonNull(fileFilters, "fileFilters");
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
IOFileFilter fileFilter = var2[var4];
this.addFileFilter(fileFilter);
}
}
public List<IOFileFilter> getFileFilters() {
return Collections.unmodifiableList(this.fileFilters);
}
private boolean isEmpty() {
return this.fileFilters.isEmpty();
}
public boolean removeFileFilter(IOFileFilter ioFileFilter) {
return this.fileFilters.remove(ioFileFilter);
}
public void setFileFilters(List<IOFileFilter> fileFilters) {
this.fileFilters.clear();
this.fileFilters.addAll(fileFilters);
}
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(super.toString());
buffer.append("(");
for(int i = 0; i < this.fileFilters.size(); ++i) {
if (i > 0) {
buffer.append(",");
}
buffer.append(this.fileFilters.get(i));
}
buffer.append(")");
return buffer.toString();
}
}
OrFileFilter
public class OrFileFilter extends AbstractFileFilter implements ConditionalFileFilter, Serializable {
private static final long serialVersionUID = 5767770777065432721L;
private final List<IOFileFilter> fileFilters;
public OrFileFilter() {
this(0);
}
private OrFileFilter(ArrayList<IOFileFilter> initialList) {
this.fileFilters = (List)Objects.requireNonNull(initialList, "initialList");
}
private OrFileFilter(int initialCapacity) {
this(new ArrayList(initialCapacity));
}
public OrFileFilter(IOFileFilter... fileFilters) {
this(((IOFileFilter[])Objects.requireNonNull(fileFilters, "fileFilters")).length);
this.addFileFilter(fileFilters);
}
public OrFileFilter(IOFileFilter filter1, IOFileFilter filter2) {
this(2);
this.addFileFilter(filter1);
this.addFileFilter(filter2);
}
public OrFileFilter(List<IOFileFilter> fileFilters) {
this(new ArrayList((Collection)Objects.requireNonNull(fileFilters, "fileFilters")));
}
public boolean accept(File file) {
Iterator var2 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var2.hasNext()) {
return false;
}
fileFilter = (IOFileFilter)var2.next();
} while(!fileFilter.accept(file));
return true;
}
public boolean accept(File file, String name) {
Iterator var3 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var3.hasNext()) {
return false;
}
fileFilter = (IOFileFilter)var3.next();
} while(!fileFilter.accept(file, name));
return true;
}
public FileVisitResult accept(Path file, BasicFileAttributes attributes) {
Iterator var3 = this.fileFilters.iterator();
IOFileFilter fileFilter;
do {
if (!var3.hasNext()) {
return FileVisitResult.TERMINATE;
}
fileFilter = (IOFileFilter)var3.next();
} while(fileFilter.accept(file, attributes) != FileVisitResult.CONTINUE);
return FileVisitResult.CONTINUE;
}
public void addFileFilter(IOFileFilter fileFilter) {
this.fileFilters.add(Objects.requireNonNull(fileFilter, "fileFilter"));
}
public void addFileFilter(IOFileFilter... fileFilters) {
IOFileFilter[] var2 = (IOFileFilter[])Objects.requireNonNull(fileFilters, "fileFilters");
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
IOFileFilter fileFilter = var2[var4];
this.addFileFilter(fileFilter);
}
}
public List<IOFileFilter> getFileFilters() {
return Collections.unmodifiableList(this.fileFilters);
}
public boolean removeFileFilter(IOFileFilter fileFilter) {
return this.fileFilters.remove(fileFilter);
}
public void setFileFilters(List<IOFileFilter> fileFilters) {
this.fileFilters.clear();
this.fileFilters.addAll((Collection)Objects.requireNonNull(fileFilters, "fileFilters"));
}
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(super.toString());
buffer.append("(");
if (this.fileFilters != null) {
for(int i = 0; i < this.fileFilters.size(); ++i) {
if (i > 0) {
buffer.append(",");
}
buffer.append(this.fileFilters.get(i));
}
}
buffer.append(")");
return buffer.toString();
}
}
NotFileFilter
public class NotFileFilter extends AbstractFileFilter implements Serializable {
private static final long serialVersionUID = 6131563330944994230L;
private final IOFileFilter filter;
public NotFileFilter(IOFileFilter filter) {
if (filter == null) {
throw new IllegalArgumentException("The filter must not be null");
} else {
this.filter = filter;
}
}
public boolean accept(File file) {
return !this.filter.accept(file);
}
public boolean accept(File file, String name) {
return !this.filter.accept(file, name);
}
public FileVisitResult accept(Path file, BasicFileAttributes attributes) {
return this.not(this.filter.accept(file, attributes));
}
private FileVisitResult not(FileVisitResult accept) {
return accept == FileVisitResult.CONTINUE ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
}
public String toString() {
return "NOT (" + this.filter.toString() + ")";
}
}