设计模式之建造者模式

模式定义

建造者模式:将一个复杂产品的创建与表示分离,使得同样的创建过程可以创建不同的表示
客户端不用去关心产品对象的内部组成,只需要关注如何一步一步的去创建复杂对象,建造者模式是一种创建型模式,何为创建型模式? 顾名思义就是创建对象的设计模式,如常用的单例模式,工厂模式等都为创建型模式。

模式结构

建造者模式主要有以下四个角色:

  • Builder 抽象建造者
  • ConcreteBuilder 具体建造者
  • Director 指挥者
  • Product 具体产品
Builder.jpg

代码分析

Product:具体创建的产品对象

public class Product {

    private String partA;
    private String partB;
    private String partC;
    
    public String getPartA() {
        return partA;
    }
    public void setPartA(String partA) {
        this.partA = partA;
    }
    public String getPartB() {
        return partB;
    }
    public void setPartB(String partB) {
        this.partB = partB;
    }
    public String getPartC() {
        return partC;
    }
    public void setPartC(String partC) {
        this.partC = partC;
    }
    
    
}

Builder:抽象建造者

public abstract class Builder {
    public Product product = new Product();
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();
    public Product getResult() {
        return product;
    }

}

ConcreteBuilder:具体建造者


public class ConcreteBuilder extends Builder{
    

    @Override
    public void buildPartA() {
        // TODO Auto-generated method stub
        product.setPartA("partA");
        
    }

    @Override
    public void buildPartB() {
        // TODO Auto-generated method stub
        product.setPartB("partB");
        
    }

    @Override
    public void buildPartC() {
        // TODO Auto-generated method stub
        product.setPartC("partC");
        
    }

}

Director:指挥者

public class Director {

    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
        
    }
    public Product construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.getResult();
    }

}

测试及结果:

public class Test {
    public static void main(String[] args) {
        
        ConcreteBuilder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
        System.out.println(product.getPartA());
        System.out.println(product.getPartB());
        System.out.println(product.getPartC());
        
    }
    

}

从测试代码来看,客户端只需要知道具体的创建者类型,并通过指挥者就可以创建想要的对象,无需关系产品内部的具体构造细节,Builder抽象定义了产品的创建方法和返回方法,而真正的实现是ConcreteBuilder,我们也可以定义不同的ConcreteBuilder,可以定义多个ConcreteBuilder。对于客户端而言,Director分离了产品的创建过程,通过调用建造者的一系列方法返回一个完整的产品对象,这样就做到了所谓的创建与表示相分离,且同样的创建过程有不同的表示。在我们日常的使用中,如果只有一个具体的建造者,通常会省略掉具体的建造者和指挥者,Builder自身充当指挥者角色,下面我们通过分析时下比较流行的网络框架Retrofit的创建过程,它就是采用Builder模式来创建对象。PS:很多不错的开源框架都用到了Builder设计模式

实例分析

接下来首先看一下retrofit对象创建的代码

 Retrofit retrofit = new Retrofit.Builder()
                            .baseUrl("http:www.baidu.com")
                            .addConverterFactory(ScalarsConverterFactory.create())
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                            .build();

retrofit对象创建采用了建造者模式,只不过它省略了ConcreteBuilder和Director这俩个角色,我们来看一下它的源码,这里并不是Retrofit这个类的所有源代码,我删除了一些跟本文内容无关的代码


public final class Retrofit {
  

  final okhttp3.Call.Factory callFactory;
  final HttpUrl baseUrl;
  final List<Converter.Factory> converterFactories;
  final List<CallAdapter.Factory> adapterFactories;
  final Executor callbackExecutor;
  final boolean validateEagerly;

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
      Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site.
    this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }


  public okhttp3.Call.Factory callFactory() {
    return callFactory;
  }

  /** The API base URL. */
  public HttpUrl baseUrl() {
    return baseUrl;
  }

  public List<CallAdapter.Factory> callAdapterFactories() {
    return adapterFactories;
  }


  public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }


  public List<Converter.Factory> converterFactories() {
    return converterFactories;
  }
  
  public Executor callbackExecutor() {
    return callbackExecutor;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }

  
  public static final class Builder {
    private final Platform platform;
    private okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    private Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
      converterFactories.add(new BuiltInConverters());
    }

    public Builder() {
      this(Platform.get());
    }

    Builder(Retrofit retrofit) {
      platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;
      converterFactories.addAll(retrofit.converterFactories);
      adapterFactories.addAll(retrofit.adapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      adapterFactories.remove(adapterFactories.size() - 1);
      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }

   
    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

   
    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

   
    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

   
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

   
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

   
    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

  
    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
  }
}

首先我们来分析下源码中的角色,其中有俩个角色,分别为Retrofit类和其静态内部类Builder,结合之前的建造者模式角色分析,这里的Retrofit其实就是我们要创建的产品Product,但是这里的Builder并不是一个抽象类,而且没有发现之前所分析的具体建造者ConcreteBuilder和指挥者Director,正如上面所说,当只有一个具体的建造者时,我们可以省略掉具体建造者这个角色,如果省略掉具体建造者,那么指挥者也可以一并省略,Builder自身充当建造者与指挥者双角色。那Builder是如何充当这俩个角色的呢?在Builder类中有一系列公开方法且返回值都是自身,其实这些方法就是对Retrofit的成员属性进行了实例化和赋值,Builder类中还有一个重要的Build方法,该方法的返回值是我们要创建的对象Retrofit,Build方法中首先对Retrofit的相关成员属性做了判空操作,最后调用了Retrofit的构造方法返回Retrofit对象并完成了对象的创建。

总结

  • 建造者模式属于创建型模式,将一个复杂产品的创建与表示相分离,同样的构建过程有不同的表示。何为复杂产品?其实就是成员属性比较多的产品,客户端无需关心复杂产品的内部构造组成细节,只需要知道具体的建造者类型即可,更多关注如何一步一步创建一个复杂对象。
  • 建造者模式优点:客户端无需知道产品内部内部构造组成细节,将产品本身与产品的创建过程解耦;具体的建造者时间相互独立,便于替换或增加新的建造者,客户端通过不同的建造者可以得到不同对象。
  • 建造者模式缺点:建造者创建的产品一般具有具有较多相同共同点,其内部组成类似,如果产品之间差异性较大,则不适合用建造者模式,其次,如果产品内部变化复杂,会导致创建很多具体的建造者来对应其变化,这样会导致系统变的很庞大。
  • 建造者模式使用场景:需要创建的产品有复杂的内部结构,也就是说成员属性较多,且产品成员属性之间相互依赖,需指定生成顺序。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容