本质
封装对象之间的组合:Director打包产品由哪些零件组合、如何组合的逻辑
封装对象创建与依赖:Builder打包一种产品中各零件的创建细节,以及产品和零件的装配关系
应用场景原理
对象由多个部分组合而成(业务可变部分)
对象有较为固定的创建流程(业务不变部分)
案例实现
Builder自定义Product
以下案例展示了Builder Pattern的一种实现:目的是读取配置文件,生成指定顺序和字段组成的list对象
Director指定Product的生成逻辑:按顺序组装三个零件形成产品
Builder指定Product的具体实现:自定义Product为List类型,实现buildPart()方法读取文件对应字段生成各零件,并将零件装配到list中
UML
接口
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.