Builder Pattern – 流程式生成对象组合

本质

封装对象之间的组合:Director打包产品由哪些零件组合、如何组合的逻辑
封装对象创建与依赖:Builder打包一种产品中各零件的创建细节,以及产品和零件的装配关系

应用场景原理

对象由多个部分组合而成(业务可变部分)
对象有较为固定的创建流程(业务不变部分)

案例实现

Builder自定义Product

以下案例展示了Builder Pattern的一种实现:目的是读取配置文件,生成指定顺序和字段组成的list对象
Director指定Product的生成逻辑:按顺序组装三个零件形成产品
Builder指定Product的具体实现:自定义Product为List类型,实现buildPart()方法读取文件对应字段生成各零件,并将零件装配到list中

UML

builder-pattern-custom

接口
public interface Director {
    void Construct(Builder builder);
}
 
public interface Builder {
    void buildPart1();
 
    void buildPart2();
 
    void buildPart3();
 
    void createProduct();
}
实现类
public class CustomDirectorImpl implements Director {
 
    @Override
    public void Construct(Builder builder) {
        builder.createProduct();
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
    }
}
 
public class CustomBuilderImpl implements Builder {
    private List<String> product;
 
    private MapBuiltFromConfiguration map = MapBuiltFromConfiguration.getInstance();
 
    @Override
    public void buildPart1() {
        putPartbyKey(MapBuiltFromConfiguration.PART_ONE_KEY);
    }
 
    @Override
    public void buildPart2() {
        putPartbyKey(MapBuiltFromConfiguration.PART_TWO_KEY);
    }
 
    @Override
    public void buildPart3() {
        putPartbyKey(MapBuiltFromConfiguration.PART_THREE_KEY);
    }
 
    @Override
    public void createProduct() {
        product = new ArrayList<>();
        map.readAttributefromConfigurationFile();
    }
 
    private void putPartbyKey(String key) {
        String val = map.getVal(key);
        if (val != null) product.add(val);
    }
 
    public List<String> getProduct() {
        return product;
    }
}
辅助工具类
public class MapBuiltFromConfiguration {
    public static final String PART_ONE_KEY = "PART1";
    public static final String PART_TWO_KEY = "PART2";
    public static final String PART_THREE_KEY = "PART3";
    private static final String READ_FILE_PATH = "src/BuilderPattern/custom/part.conf";
    private static final String GET_PART_REGEX = "(PART\\d+)\\s*:\\s*(.+)";
    private HashMap<String, String> map = new HashMap<>();
 
    private static MapBuiltFromConfiguration bean = null;
 
    public static MapBuiltFromConfiguration getInstance() {
        return bean == null ? new MapBuiltFromConfiguration() : bean;
    }
 
    public void readAttributefromConfigurationFile() {
        String line = null;
        FileReader fileReader = null;
        BufferedReader bufferedReader = null;
 
        try {
            fileReader = new FileReader(READ_FILE_PATH);
            bufferedReader = new BufferedReader(fileReader);
            while ((line = bufferedReader.readLine()) != null)
                analyzeLineAndBuildMap(line);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fileReader != null) fileReader.close();
                if (bufferedReader != null) bufferedReader.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    private void analyzeLineAndBuildMap(String line) {
        Pattern pattern = Pattern.compile(GET_PART_REGEX);
        Matcher matcher = pattern.matcher(line);
        if (matcher.find()) {
            String key = matcher.group(1);
            String val = matcher.group(2);
            map.put(key, val);
        }
    }
 
    public String getVal(String key) {
        return map.get(key);
    }
}
测试类
public class Client {
    public static void main(String[] args) {
        CustomBuilderImpl builder = new CustomBuilderImpl();
        Director director = new CustomDirectorImpl();
        director.Construct(builder);
        List<String> product = builder.getProduct();
        System.out.println(product);
    }
}

详细说明

Builder和Director为什么要分开?

合并对象的创建流程(Director)和创建步骤(Builder),即模板方法模式

开闭原则 支持流程业务变更。当流程需求变更时,模板方法模式需要打开类修改或增加流程方法;而分开时,仅需新增一个自定义流程类而无需更改任何已有部分

单一职责 拆分会降低单一类的复杂度,同时让类的职责更为清晰

不同的director可以使用不同的builder

director() {
buildpart1
buildpart3
buildpart2
}
abstract buildpart1
abstract buildpart2
abstract buildpart3

为什么不创建Product接口?

首先明确product的创建流程:
1)在Builder中创建product实例
2)依据Director提供的流程,将Builder中的buildPart()生成的各组成部分装配入product
3)最终由getProduct()返回实例

回到问题,创不创建Product接口取决于业务:
1)如果业务保证product实例满足同一个标准,完全可以在上述1)3)中使用Product接口,进一步解耦并提升复用(详见上例ProcessDirectorImpl)
2)如果业务中product在满足统一创建流程的基础上,组成结构不完全一致,此时1)3)处不使用接口会给予更多灵活性:Builder可以创造完全由Builder自定义的Product(详见上例CustomBuilderImpl)

参考资料

Gamma E. 设计模式:可复用面向对象软件的基础[M]. 1. 机械工业出版社, 2019.

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

©2018-2024 Howell版权所有 备案号:冀ICP备19000576号