起因是学习okhttp过程中遇到的这段代码
- Request request = original.newBuilder()
- .url(original.url())
- .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
- .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
- .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
- .header("Accept", Configuration.SSE_CONTENT_TYPE)
- .method(original.method(), original.body())
- .build();
newBuilder().build()创建对象的方式以前没注意过,搜了一下,这种方法属于广义上的建造者模式,于是进行学习。
我自己概括建造者模式的思想:把一个整体A,分割成多个部分A1,A2,A3,A4。每个部分分开建造,最后组装成一个整体。
设想这样的场景,装修一个房子,需要考虑吊顶、涂料、地板、瓷砖等部分。每个部分都有不同的品牌选择。
装修公司提供一些成套的、整体装修方案,可以看作是这些部分的排列组合。
我们用代码进行描述。以下是代码结构
模块builder-pattern-01中,Matter是材料的父接口,定义了材料的各种属性。
- public interface Matter {
- /**
- * 场景:ceiling、coat、floor、tile
- * @return 吊顶、涂料、地板、瓷砖
- */
- String scene();
-
- /**
- * 品牌
- * @return 自定字符串
- */
- String brand();
-
- /**
- * 型号
- * @return 自定义字符串
- */
- String model();
-
- /**
- * 单价(平米报价)
- * @return BigDecimal
- */
- BigDecimal price();
-
- /**
- * 描述
- * @return 自定义字符串
- */
- String desc();
- }
ceiling、coat、floor、tile分别表示四个部分:吊顶、涂料、地板、瓷砖。
这四个部分有不同的材料。这些材料组合装修,成为一个整体方案。代码略。
builder-pattern-02中,IMenu是装修包接口,用于添加材料;DecoratiuonPackageMenu是其实现。
- /**
- * 装修包接口
- * 用于添加材料
- */
- public interface IMenu {
- /**
- * 添加吊顶
- * @param matter 材料
- * @return 装修包
- */
- IMenu appendCeiling(Matter matter);
-
- /**
- * 添加涂料
- * @param matter 材料
- * @return 装修包
- */
- IMenu appendCoat(Matter matter);
-
- /**
- * 添加地板
- * @param matter 材料
- * @return 装修包
- */
- IMenu appendFloor(Matter matter);
-
- /**
- * 添加瓷砖
- * @param matter 材料
- * @return 装修包
- */
- IMenu appendTile(Matter matter);
-
- /**
- * 获取装修包信息
- * @return 装修包detail
- */
- String getDetail();
- }
- public class DecorationPackageMenu implements IMenu{
-
- /** 材料清单 */
- private List
list = new ArrayList<>(16); - /** 总价格 */
- private BigDecimal price = BigDecimal.ZERO;
- /** 面积 */
- private BigDecimal area;
- /** 装修等级 */
- private String grade;
-
- public DecorationPackageMenu(){}
- public DecorationPackageMenu(double area, String grade) {
- this.area = new BigDecimal(area);
- this.grade = grade;
- }
-
- @Override
- public IMenu appendCeiling(Matter matter) {
- list.add(matter);
- // 注意这个price=赋值操作,单纯的add等运算不会改变原来的值
- // price = price + ( area * (unitPrice * matter.price()) )
- price = price.add(area.multiply(matter.price()));
- return this;
- }
-
- @Override
- public IMenu appendCoat(Matter matter) {
- list.add(matter);
- price = price.add(area.multiply(matter.price()));
- return this;
- }
-
- @Override
- public IMenu appendFloor(Matter matter) {
- list.add(matter);
- price = price.add(area.multiply(matter.price()));
- return this;
- }
-
- @Override
- public IMenu appendTile(Matter matter) {
- list.add(matter);
- price = price.add(area.multiply(matter.price()));
- return this;
- }
-
- @Override
- public String getDetail() {
- StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
- "装修清单: " + "\r\n" +
- "套餐等级: " + grade + "\r\n" +
- "套餐价格: " + price.setScale(2, BigDecimal.ROUND_HALF_UP) + "元\r\n" +
- "房屋面积: " + area.doubleValue() + "平方米\r\n" +
-
- "材料清单: " + "\r\n" );
- for (Matter matter : list) {
- detail.append("场景:").append(matter.scene()).append("、")
- .append("品牌:").append(matter.brand()).append("、")
- .append("类别:").append(matter.model()).append("、")
- .append("平米价格:").append(matter.price()).append("元\r\n");
- }
- return detail.toString();
- }
- }
builder是建造者,使用IMenu装修包对各种材料进行组装
- public class Builder {
- public IMenu levelOne(Double area) {
- return new DecorationPackageMenu(area, "豪华欧式")
- .appendCeiling(new LevelOneCeiling())
- .appendCoat(new DuluxCoat())
- .appendFloor(new DerFloor())
- .appendTile(new DongPengTile());
- }
-
- public IMenu levelTwo(Double area) {
- return new DecorationPackageMenu(area, "轻奢田园")
- .appendCeiling(new LevelTwoCeiling())
- .appendCoat(new NipponCoat())
- .appendFloor(new ShengXiangFloor())
- .appendTile(new MarcoPoloTile());
- }
-
- public IMenu levelThree(Double area) {
- return new DecorationPackageMenu(area, "现代简约")
- .appendCeiling(new LevelOneCeiling())
- .appendCoat(new NipponCoat())
- .appendFloor(new ShengXiangFloor())
- .appendTile(new DongPengTile());
- }
- }
我们使用单元测试进行测试一下
- @Test
- public void test_builder() {
- Builder builder = new Builder();
- System.out.println(builder.levelOne(132.5D).getDetail());
- System.out.println(builder.levelTwo(150.0D).getDetail());
- System.out.println(builder.levelThree(170.0D).getDetail());
- }
结果部分为
其余略
建造者模式思想基本领略,那么okhttp的newBuilder().build()创建对象的思想跟上面差不多。
那么对于这段代码,
- Request request = original.newBuilder()
- .url(original.url())
- .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
- .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
- .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
- .header("Accept", Configuration.SSE_CONTENT_TYPE)
- .method(original.method(), original.body())
- .build();
我们可以进行学习newBuilder().build()这种创建对象的方式,先来看看其源码
- public Builder newBuilder() {
- return new Builder(this);
- }
-
- public Request build() {
- if (this.url == null) {
- throw new IllegalStateException("url == null");
- } else {
- return new Request(this);
- }
- }
-
- public Builder url(HttpUrl url) {
- if (url == null) {
- throw new NullPointerException("url == null");
- } else {
- this.url = url;
- return this;
- }
- }
-
- public Builder header(String name, String value) {
- this.headers.set(name, value);
- return this;
- }
-
- public Builder method(String method, @Nullable RequestBody body) {
- if (method == null) {
- throw new NullPointerException("method == null");
- } else if (method.length() == 0) {
- throw new IllegalArgumentException("method.length() == 0");
- } else if (body != null && !HttpMethod.permitsRequestBody(method)) {
- throw new IllegalArgumentException("method " + method + " must not have a request body.");
- } else if (body == null && HttpMethod.requiresRequestBody(method)) {
- throw new IllegalArgumentException("method " + method + " must have a request body.");
- } else {
- this.method = method;
- this.body = body;
- return this;
- }
- }
-
通过newBuilder()创建了一个Builder类的对象,然后每次url() header()等方法修改属性,都是在对Builder类的对象进行修改,最后build()方法 通过Builder类的对象创建了Request对象。
这种基于不可变对象(或者为了不改变原对象)而创建新对象的方式也值得我们学习。
基于此思想,创建一个类Message
- @Data
- public class Message implements Serializable {
- private String role;
- private String content;
- private String name;
-
- public Message(){}
- public Message(Builder builder) {
- this.role = builder.role;
- this.content = builder.content;
- this.name = builder.name;
- }
-
- /**
- * 注意这里 static方法
- */
- public static Builder builder() {
- return new Builder();
- }
-
- /**
- * 建造者模式
- */
- public static final class Builder {
- private String role;
- private String content;
- private String name;
- public Builder(){}
- public Builder role(Constants.Role role) {
- this.role = role.getCode();
- return this;
- }
- public Builder content(String content) {
- this.content = content;
- return this;
- }
- public Builder name(String name) {
- this.name = name;
- return this;
- }
- public Message build() {
- return new Message(this);
- }
- }
- }
得注意build()方法是在最后使用的,所以应该是Builder的方法,并且返回值类型应该是Message类。
补充,后来才发现lombok有@Builder注解,就是这个方法......