一、介绍

建造者模式(Builder Pattern)也称生成器模式,它属于创建型模式。它的名词解释如下:

separate the construction of a complex object from its representation so that the same construction process can create different representations.

即:将复杂对象的构造与它的表示分开,这样可以在相同的构造过程中创建不同的表示形式。

UML图:

设计模式—建造者模式及实例(BuilderPattern)_第1张图片


如上图所示,经典的Builder模式包含了四个角色,分别是:

  1. 产品(Product)角色
      由一系列部件组成,一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。

  2. 抽象建造者(Builder)角色
      给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者 (ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的两种方法:一种是建造方法(buildPart),另一种是返还结构方法(getResult)。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少零件,就有多少相应的建造方法。
      引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。

  3. 具体建造者(ConcreteBuilder)角色
      实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。

  4. 导演者(Director)角色
      负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变、及其复杂的部分。导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。



通俗地来讲,假如想构建一个对象,通过告诉构建者,自己对这个对象的各种要求,然后建造者根据这些要求进行处理,生成所需要的一个对象。打个比方,我们现在需要一个用户信息对象,里面包含姓名,年龄,性别,身高,体重,职业...等等信息。但我们可能只需要其中的部分信息,如果对象包含的参数很多时,则根据排列组合起来,构造方法将多得不敢想象。而利用建造者模式,则具体化各对象部分的建造,最后一次性返回建造好的用户信息对象


1. 建造者模式的优点

  • 封装性

使用建造者模式可以使客户端不必知道产品内部组成的细节,如例子中我们就不需要关心每一个具体的模型内部是如何实现的,产生的对象类型就是CarModel。

  • 建造者独立,容易扩展

Builder之间是相互独立的,与其它的Builder无关,耦合度低,对系统的扩展非常有利。

  • 便于控制细节风险

模式所建造的最终产品更易于控制:由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。


2. 建造者模式的缺点

  • 可能会产生多余的Builder对象以及Director对象;

  • 对象的构建过程暴露。

3. 建造者模式的适用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式,需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序

  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。

  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式是非常合适。

  • 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景,只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建过程,本身已经违反设计最初目标。

二、在Android中的应用案例


在Android开发中,常见的有系统对话框AlertDialog、Notification、网络请求开源库Okhttp、图片加载缓存库Glide、Picasso 等都利用了建造者模式。它们的具体源码就不贴了,有兴趣的可自行阅读研究源码。刚好最近在研究Gilde的源码,这里给出Glide 中的Gildebuilder类用以展示:

package com.bumptech.glide;import android.content.Context;import android.os.Build;import com.bumptech.glide.load.DecodeFormat;import com.bumptech.glide.load.engine.Engine;import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;import com.bumptech.glide.load.engine.cache.DiskCache;import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;import com.bumptech.glide.load.engine.cache.LruResourceCache;import com.bumptech.glide.load.engine.cache.MemoryCache;import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;import java.util.concurrent.ExecutorService;/** * A builder class for setting default structural classes for Glide to use. */public class GlideBuilder {    private final Context context;    private Engine engine;    private BitmapPool bitmapPool;    private MemoryCache memoryCache;    private ExecutorService sourceService;    private ExecutorService diskCacheService;    private DecodeFormat decodeFormat;    private DiskCache.Factory diskCacheFactory;    public GlideBuilder(Context context) {        this.context = context.getApplicationContext();    }    /**     * Sets the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation to use to store and     * retrieve reused {@link android.graphics.Bitmap}s.     *     * @param bitmapPool The pool to use.     * @return This builder.     */    public GlideBuilder setBitmapPool(BitmapPool bitmapPool) {        this.bitmapPool = bitmapPool;        return this;    }    /**     * Sets the {@link com.bumptech.glide.load.engine.cache.MemoryCache} implementation to store     * {@link com.bumptech.glide.load.engine.Resource}s that are not currently in use.     *     * @param memoryCache  The cache to use.     * @return This builder.     */    public GlideBuilder setMemoryCache(MemoryCache memoryCache) {        this.memoryCache = memoryCache;        return this;    }    /**     * Sets the {@link com.bumptech.glide.load.engine.cache.DiskCache} implementation to use to store     * {@link com.bumptech.glide.load.engine.Resource} data and thumbnails.     *     * @deprecated Creating a disk cache directory on the main thread causes strict mode violations, use     * {@link #setDiskCache(com.bumptech.glide.load.engine.cache.DiskCache.Factory)} instead. Scheduled to be removed     * in Glide 4.0.     * @param diskCache The disk cache to use.     * @return This builder.     */    @Deprecated    public GlideBuilder setDiskCache(final DiskCache diskCache) {        return setDiskCache(new DiskCache.Factory() {            @Override            public DiskCache build() {                return diskCache;            }        });    }    /**     * Sets the {@link com.bumptech.glide.load.engine.cache.DiskCache.Factory} implementation to use to construct     * the {@link com.bumptech.glide.load.engine.cache.DiskCache} to use to store     * {@link com.bumptech.glide.load.engine.Resource} data on disk.     *     * @param diskCacheFactory The disk cche factory to use.     * @return This builder.     */    public GlideBuilder setDiskCache(DiskCache.Factory diskCacheFactory) {        this.diskCacheFactory = diskCacheFactory;        return this;    }    /**     * Sets the {@link java.util.concurrent.ExecutorService} implementation to use when retrieving     * {@link com.bumptech.glide.load.engine.Resource}s that are not already in the cache.     *     * <p>     *     Any implementation must order requests based on their {@link com.bumptech.glide.Priority} for thumbnail     *     requests to work properly.     * </p>     *     * @see #setDiskCacheService(java.util.concurrent.ExecutorService)     * @see com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor     *     * @param service The ExecutorService to use.     * @return This builder.     */    public GlideBuilder setResizeService(ExecutorService service) {        this.sourceService = service;        return this;    }    /**     * Sets the {@link java.util.concurrent.ExecutorService} implementation to use when retrieving     * {@link com.bumptech.glide.load.engine.Resource}s that are currently in cache.     *     * <p>     *     Any implementation must order requests based on their {@link com.bumptech.glide.Priority} for thumbnail     *     requests to work properly.     * </p>     *     * @see #setResizeService(java.util.concurrent.ExecutorService)     * @see com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor     *     * @param service The ExecutorService to use.     * @return This builder.     */    public GlideBuilder setDiskCacheService(ExecutorService service) {        this.diskCacheService = service;        return this;    }    /**     * Sets the {@link com.bumptech.glide.load.DecodeFormat} that will be the default format for all the default     * decoders that can change the {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap}s they     * decode.     *     * <p>     *     Decode format is always a suggestion, not a requirement. See {@link com.bumptech.glide.load.DecodeFormat} for     *     more details.     * </p>     *     * <p>     *     If you instantiate and use a custom decoder, it will use     *     {@link com.bumptech.glide.load.DecodeFormat#DEFAULT} as its default.     * </p>     *     * <p>     *     Calls to this method are ignored on KitKat and Lollipop. See #301.     * </p>     *     * @param decodeFormat The format to use.     * @return This builder.     */    public GlideBuilder setDecodeFormat(DecodeFormat decodeFormat) {        this.decodeFormat = decodeFormat;        return this;    }    // For testing.    GlideBuilder setEngine(Engine engine) {        this.engine = engine;        return this;    }    Glide createGlide() {        if (sourceService == null) {            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());            sourceService = new FifoPriorityThreadPoolExecutor(cores);        }        if (diskCacheService == null) {            diskCacheService = new FifoPriorityThreadPoolExecutor(1);        }        MemorySizeCalculator calculator = new MemorySizeCalculator(context);        if (bitmapPool == null) {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {                int size = calculator.getBitmapPoolSize();                bitmapPool = new LruBitmapPool(size);            } else {                bitmapPool = new BitmapPoolAdapter();            }        }        if (memoryCache == null) {            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());        }        if (diskCacheFactory == null) {            diskCacheFactory = new InternalCacheDiskCacheFactory(context);        }        if (engine == null) {            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);        }        if (decodeFormat == null) {            decodeFormat = DecodeFormat.DEFAULT;        }        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);    }}

三、Builder模式变种(链式调用)

平时使用的场景和频率也非常多,我们可能会是这样:

1.通过构造函数的参数形式去写一个实现类

UserInfo(String name);

UserInfo(String name ,String sex);

UserInfo(String name ,String sex, int age);

UserInfo(String name ,String sex, int age, double height);

UserInfo(String name ,String sex, int age, double height, double weight);


2.设置setter和getter方法的形式:

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public double getHeight() {
return height;
}

public void setHeight(double height) {
this.height = height;
}

public double getWeight() {
return weight;
}

public void setWeight(double weight) {
this.weight = weight;
}

在参数不多的情况下,利用构造方法的方式是比较方便快捷的,一旦参数多了,代码可读性大大降低,并且难以维护,对调用者来说也造成一定困惑;

而设置setter和getter可读性不错,也易于维护,但是这样子做对象会产生不一致的状态,当你想要传入全部参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实User对象并没有创建完成。

因此,利用Bulider模式来创建复杂的对象,就会合适很多。这里我就自己写了个demo,以方便大家更为直观地理解它的思路,GitHub项目地址:Builder模式demo项目

代码如下:

1.定义UserBuilder类:

package com.builderdemo;import android.widget.TextView;/** * TODO<用户信息的建造者> * * @author: 小嵩 * @date: 2017/2/7 16:05 * @version: V1.0 */public class UserInfoBuilder {    private String name;  //姓名 (必填,在初始化时,传入参数)    private String sex;  //性别    private int age;      //年龄    private double height; //身高CM    private double weight; //体重KG    public UserInfoBuilder(String name){        this.name = name;    }        public UserInfoBuilder setSex(String sex){            this.sex = sex;            return this;        }        public UserInfoBuilder setAge(int age){            this.age = age;            return this;        }        public UserInfoBuilder setHeight(double height){            this.height = height;            return this;        }        public UserInfoBuilder setWeight(double weight){            this.weight = weight;            return this;        }        public UserInfoBuilder into(TextView textView){            textView.setText("姓名:"+name+"\n性别:"+sex+"\n年龄:"+age+"\n身高:"+height+"\n体重:"+weight);            return this;        }        public UserInfo create(){            return new UserInfo(name, sex, age, height, weight);        }}

2.UserInfo 类:

package com.builderdemo;/** * TODO<用户信息> * * @author: 小嵩 * @date: 2017/2/7 15:32 * @version: V1.0 */public class UserInfo {    private String name;  //姓名(必填)    private String sex;  //性别    private int age;      //年龄    private double height; //身高CM    private double weight; //体重KG    public UserInfo(String name, String sex, int age, double height, double weight) {        this.name = name;        this.sex = sex;        this.age = age;        this.height = height;        this.weight = weight;    }    @Override    public String toString() {        return "UserInfo{" +                "name='" + name + '\'' +                ", sex='" + sex + '\'' +                ", age=" + age +                ", height=" + height +                ", weight=" + weight +                '}';    }}

需要注意的是,如果某些参数必须设定,那么我们则可定义一个Builder的构造方法,在初始化Builder的时候,就传入参数进去,像这样:

public class UserInfoBuilder {    private String name;  //姓名 (必填,在初始化时,传入参数)    private String sex;  //性别    private int age;      //年龄    private double height; //身高CM    private double weight; //体重KG    public UserInfoBuilder(String name){        this.name = name;    }    ...

关于链式调用,它的关键其实是在set方法中,return builder对象本身,这样在调用方法时就能返回对象本身,不用打分号继续调用其他方法,像这样:

new UserInfoBuilder("小嵩")        .setAge(23)        .setSex("")        .setHeight(174)        .setWeight(62.5)        .into(tv_content);//显示到TextView
而不用每次都这样:

UserInfoBuilder builder = new UserInfoBuilder("小嵩");builder.setAge(23);builder.setSex("");


四、经典 Builder模式 (简单实例代码)


具体产品类:

package designpatterns.builder;// produce to be builtclass Starbucks {    private String size;    private String drink;    public void setSize(String size) {        this.size = size;    }    public void setDrink(String drink) {        this.drink = drink;    }}


抽象Builder类:

//abstract builderabstract class StarbucksBuilder {    protected Starbucks starbucks;    public Starbucks getStarbucks() {        return starbucks;    }    public void createStarbucks() {        starbucks = new Starbucks();        System.out.println("a drink is created");    }    public abstract void buildSize();    public abstract void buildDrink();}


具体Builder类:

// Concrete Builder to build teaclass TeaBuilder extends StarbucksBuilder {    public void buildSize() {        starbucks.setSize("large");        System.out.println("build large size");    }    public void buildDrink() {        starbucks.setDrink("tea");        System.out.println("build tea");    }}

导演者(Director)类:

//director to encapsulate the builderclass Waiter {    private StarbucksBuilder starbucksBuilder;    public void setStarbucksBuilder(StarbucksBuilder builder) {        starbucksBuilder = builder;    }    public Starbucks getstarbucksDrink() {        return starbucksBuilder.getStarbucks();    }    public void constructStarbucks() {        starbucksBuilder.createStarbucks();        starbucksBuilder.buildDrink();        starbucksBuilder.buildSize();    }} 


客户端调用:

//customerpublic class Customer {    public static void main(String[] args) {        Waiter waiter = new Waiter();        StarbucksBuilder coffeeBuilder = new CoffeeBuilder();        //Alternatively you can use tea builder to build a tea        //StarbucksBuilder teaBuilder = new TeaBuilder();        waiter.setStarbucksBuilder(coffeeBuilder);        waiter.constructStarbucks();        //get the drink built        Starbucks drink = waiter.getstarbucksDrink();    }}




参考的文章: 1.Java Design Pattern: Builder


2.http://www.jianshu.com/p/f3cf42416dff



更多相关文章

  1. Android中Task任务栈的四种模式
  2. Android全屏模式,沉浸模式。粘性沉浸模式
  3. android打开文件方法
  4. ArcGIS for Android 中MapView截图实现方法
  5. Android:指定分辨率和清晰度的图片压缩方法源码
  6. Android简单获取经纬度的方法
  7. Android拨打电话的两种实现方法
  8. android 基本的画图方法
  9. Handler 内部类导致的内存泄露修改方法

随机推荐

  1. 在android画面切换时设置跟随变动的小圆
  2. Android(安卓)studio 获取每次编译apk时
  3. android微信视频播放填坑指南
  4. FFMPEG android 多CPU架构快速编译方案
  5. Android(安卓)针对个人开发者的bmob支付
  6. Android利用BottomNavigationView默认底
  7. [置顶] 【一步一个脚印】Tomcat+MySQL为
  8. Android面试整理
  9. android Apk打包过程概述_android是如何
  10. Android(安卓)网络框架之Retrofit2使用详