设计模式—建造者模式及实例(BuilderPattern)

一、介绍

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

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

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

UML图:



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

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

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

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

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



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


1. 建造者模式的优点

  • 封装性好

      通过建造者模式可以让调用方不必知道产品内部组成的细节,将对象的配置与实现隔离。

  • 容易扩展

      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 built
class 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 builder
abstract 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 tea
class 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 builder
class 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();
    }
}
 


客户端调用:

//customer
public 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





已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页