• [设计模式] 建造者模式


    一、引言

    起因是学习okhttp过程中遇到的这段代码

    1. Request request = original.newBuilder()
    2. .url(original.url())
    3. .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
    4. .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
    5. .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
    6. .header("Accept", Configuration.SSE_CONTENT_TYPE)
    7. .method(original.method(), original.body())
    8. .build();

    newBuilder().build()创建对象的方式以前没注意过,搜了一下,这种方法属于广义上的建造者模式,于是进行学习。

    二、介绍

    我自己概括建造者模式的思想:把一个整体A,分割成多个部分A1,A2,A3,A4。每个部分分开建造,最后组装成一个整体。

    设想这样的场景,装修一个房子,需要考虑吊顶、涂料、地板、瓷砖等部分。每个部分都有不同的品牌选择。

    装修公司提供一些成套的、整体装修方案,可以看作是这些部分的排列组合。

    三、代码

    我们用代码进行描述。以下是代码结构

    模块builder-pattern-01中,Matter是材料的父接口,定义了材料的各种属性。

    1. public interface Matter {
    2. /**
    3. * 场景:ceiling、coat、floor、tile
    4. * @return 吊顶、涂料、地板、瓷砖
    5. */
    6. String scene();
    7. /**
    8. * 品牌
    9. * @return 自定字符串
    10. */
    11. String brand();
    12. /**
    13. * 型号
    14. * @return 自定义字符串
    15. */
    16. String model();
    17. /**
    18. * 单价(平米报价)
    19. * @return BigDecimal
    20. */
    21. BigDecimal price();
    22. /**
    23. * 描述
    24. * @return 自定义字符串
    25. */
    26. String desc();
    27. }

     ceiling、coat、floor、tile分别表示四个部分:吊顶、涂料、地板、瓷砖。

    这四个部分有不同的材料。这些材料组合装修,成为一个整体方案。代码略。

    builder-pattern-02中,IMenu是装修包接口,用于添加材料;DecoratiuonPackageMenu是其实现。
     

    1. /**
    2. * 装修包接口
    3. * 用于添加材料
    4. */
    5. public interface IMenu {
    6. /**
    7. * 添加吊顶
    8. * @param matter 材料
    9. * @return 装修包
    10. */
    11. IMenu appendCeiling(Matter matter);
    12. /**
    13. * 添加涂料
    14. * @param matter 材料
    15. * @return 装修包
    16. */
    17. IMenu appendCoat(Matter matter);
    18. /**
    19. * 添加地板
    20. * @param matter 材料
    21. * @return 装修包
    22. */
    23. IMenu appendFloor(Matter matter);
    24. /**
    25. * 添加瓷砖
    26. * @param matter 材料
    27. * @return 装修包
    28. */
    29. IMenu appendTile(Matter matter);
    30. /**
    31. * 获取装修包信息
    32. * @return 装修包detail
    33. */
    34. String getDetail();
    35. }
    1. public class DecorationPackageMenu implements IMenu{
    2. /** 材料清单 */
    3. private List list = new ArrayList<>(16);
    4. /** 总价格 */
    5. private BigDecimal price = BigDecimal.ZERO;
    6. /** 面积 */
    7. private BigDecimal area;
    8. /** 装修等级 */
    9. private String grade;
    10. public DecorationPackageMenu(){}
    11. public DecorationPackageMenu(double area, String grade) {
    12. this.area = new BigDecimal(area);
    13. this.grade = grade;
    14. }
    15. @Override
    16. public IMenu appendCeiling(Matter matter) {
    17. list.add(matter);
    18. // 注意这个price=赋值操作,单纯的add等运算不会改变原来的值
    19. // price = price + ( area * (unitPrice * matter.price()) )
    20. price = price.add(area.multiply(matter.price()));
    21. return this;
    22. }
    23. @Override
    24. public IMenu appendCoat(Matter matter) {
    25. list.add(matter);
    26. price = price.add(area.multiply(matter.price()));
    27. return this;
    28. }
    29. @Override
    30. public IMenu appendFloor(Matter matter) {
    31. list.add(matter);
    32. price = price.add(area.multiply(matter.price()));
    33. return this;
    34. }
    35. @Override
    36. public IMenu appendTile(Matter matter) {
    37. list.add(matter);
    38. price = price.add(area.multiply(matter.price()));
    39. return this;
    40. }
    41. @Override
    42. public String getDetail() {
    43. StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
    44. "装修清单: " + "\r\n" +
    45. "套餐等级: " + grade + "\r\n" +
    46. "套餐价格: " + price.setScale(2, BigDecimal.ROUND_HALF_UP) + "元\r\n" +
    47. "房屋面积: " + area.doubleValue() + "平方米\r\n" +
    48. "材料清单: " + "\r\n" );
    49. for (Matter matter : list) {
    50. detail.append("场景:").append(matter.scene()).append("、")
    51. .append("品牌:").append(matter.brand()).append("、")
    52. .append("类别:").append(matter.model()).append("、")
    53. .append("平米价格:").append(matter.price()).append("元\r\n");
    54. }
    55. return detail.toString();
    56. }
    57. }

    builder是建造者,使用IMenu装修包对各种材料进行组装

    1. public class Builder {
    2. public IMenu levelOne(Double area) {
    3. return new DecorationPackageMenu(area, "豪华欧式")
    4. .appendCeiling(new LevelOneCeiling())
    5. .appendCoat(new DuluxCoat())
    6. .appendFloor(new DerFloor())
    7. .appendTile(new DongPengTile());
    8. }
    9. public IMenu levelTwo(Double area) {
    10. return new DecorationPackageMenu(area, "轻奢田园")
    11. .appendCeiling(new LevelTwoCeiling())
    12. .appendCoat(new NipponCoat())
    13. .appendFloor(new ShengXiangFloor())
    14. .appendTile(new MarcoPoloTile());
    15. }
    16. public IMenu levelThree(Double area) {
    17. return new DecorationPackageMenu(area, "现代简约")
    18. .appendCeiling(new LevelOneCeiling())
    19. .appendCoat(new NipponCoat())
    20. .appendFloor(new ShengXiangFloor())
    21. .appendTile(new DongPengTile());
    22. }
    23. }

    我们使用单元测试进行测试一下

    1. @Test
    2. public void test_builder() {
    3. Builder builder = new Builder();
    4. System.out.println(builder.levelOne(132.5D).getDetail());
    5. System.out.println(builder.levelTwo(150.0D).getDetail());
    6. System.out.println(builder.levelThree(170.0D).getDetail());
    7. }

    结果部分为
     

    其余略

    四、回归okhttp

    建造者模式思想基本领略,那么okhttp的newBuilder().build()创建对象的思想跟上面差不多。

    那么对于这段代码,

    1. Request request = original.newBuilder()
    2. .url(original.url())
    3. .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
    4. .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
    5. .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
    6. .header("Accept", Configuration.SSE_CONTENT_TYPE)
    7. .method(original.method(), original.body())
    8. .build();

    我们可以进行学习newBuilder().build()这种创建对象的方式,先来看看其源码

    1. public Builder newBuilder() {
    2. return new Builder(this);
    3. }
    4. public Request build() {
    5. if (this.url == null) {
    6. throw new IllegalStateException("url == null");
    7. } else {
    8. return new Request(this);
    9. }
    10. }
    11. public Builder url(HttpUrl url) {
    12. if (url == null) {
    13. throw new NullPointerException("url == null");
    14. } else {
    15. this.url = url;
    16. return this;
    17. }
    18. }
    19. public Builder header(String name, String value) {
    20. this.headers.set(name, value);
    21. return this;
    22. }
    23. public Builder method(String method, @Nullable RequestBody body) {
    24. if (method == null) {
    25. throw new NullPointerException("method == null");
    26. } else if (method.length() == 0) {
    27. throw new IllegalArgumentException("method.length() == 0");
    28. } else if (body != null && !HttpMethod.permitsRequestBody(method)) {
    29. throw new IllegalArgumentException("method " + method + " must not have a request body.");
    30. } else if (body == null && HttpMethod.requiresRequestBody(method)) {
    31. throw new IllegalArgumentException("method " + method + " must have a request body.");
    32. } else {
    33. this.method = method;
    34. this.body = body;
    35. return this;
    36. }
    37. }

    通过newBuilder()创建了一个Builder类的对象,然后每次url() header()等方法修改属性,都是在对Builder类的对象进行修改,最后build()方法 通过Builder类的对象创建了Request对象。

    这种基于不可变对象(或者为了不改变原对象)而创建新对象的方式也值得我们学习。

    基于此思想,创建一个类Message

    1. @Data
    2. public class Message implements Serializable {
    3. private String role;
    4. private String content;
    5. private String name;
    6. public Message(){}
    7. public Message(Builder builder) {
    8. this.role = builder.role;
    9. this.content = builder.content;
    10. this.name = builder.name;
    11. }
    12. /**
    13. * 注意这里 static方法
    14. */
    15. public static Builder builder() {
    16. return new Builder();
    17. }
    18. /**
    19. * 建造者模式
    20. */
    21. public static final class Builder {
    22. private String role;
    23. private String content;
    24. private String name;
    25. public Builder(){}
    26. public Builder role(Constants.Role role) {
    27. this.role = role.getCode();
    28. return this;
    29. }
    30. public Builder content(String content) {
    31. this.content = content;
    32. return this;
    33. }
    34. public Builder name(String name) {
    35. this.name = name;
    36. return this;
    37. }
    38. public Message build() {
    39. return new Message(this);
    40. }
    41. }
    42. }

    得注意build()方法是在最后使用的,所以应该是Builder的方法,并且返回值类型应该是Message类。

    补充,后来才发现lombok有@Builder注解,就是这个方法......

    @Builder

  • 相关阅读:
    酷开科技夯实流量基础,构建智慧生活新风尚!
    136. 只出现一次的数字(hot100)
    Java容器(arraylist+vector源码+stack)
    基于STM32的自由度云台运动姿态控制系统
    【牛客网刷题】VL5-VL7位拆分与运算、数据处理器、求差值
    Java中ArrayList、LinkedList和Vector的底层原理
    c语言 static
    想买个深度学习的算力设备,TOPs和TFLOPs 啥啥分不清
    设计模式(自学)
    MySQL系列:索引失效场景总结
  • 原文地址:https://blog.csdn.net/qq_40592576/article/details/134295989