Composite概述

Composite - 图1

与其他模式区别

复合模式和装饰器模式看上去也非常相似。复合模式中的组件会形成树状的嵌套层次关系,一个组件往往不会去修改另一个组件的输出,而是聚合每个组件的功能,或聚合每个组件的输出得到一个最终的输出,复合模式经常用来开发表达式组件,例如Where,And,Or, Not这种可以灵活嵌套的复合表达式。而装饰器模式是一种链式的嵌套关系,一个装饰器往往基于被装饰的组件的输出,每一个装饰器处理一部分功能,再交给下一个装饰器继续处理。

玩具代码案例 - 文件过滤器

复合组件

ICompositeFilter

  1. package online.javabook.gof.structural.patterns3.composite.file.filter.composite;
  2. import online.javabook.gof.structural.patterns3.composite.file.filter.IFilter;
  3. public interface ICompositeFilter extends IFilter {
  4. public void addFilter(IFilter filter);
  5. public void delFilter(IFilter filter);
  6. }

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() + ")";
    }
}