建造者模式——由部分到整体构建对象

文章目录

    • 1. 定义
    • 2. 设计
      • 2.1. 有抽象建造者
      • 2.2. 无抽象建造者
    • 3. 应用
      • 3.1. 复杂对象构建举例
      • 3.2. 多参数方法改良举例
      • 3.3. JDK:Locale
      • 3.4. JDK:StringBuilder
      • 3.5. Gson:GsonBuilder
      • 3.6. OkHttp:OkHttpClient.Builder
    • 4. 特点
      • 4.1. 优势
      • 4.2. 缺点
      • 4.3. 注意事项
      • 4.4. 和抽象工厂模式的区别

Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

可以用来简化复杂对象的构造过程。

对比现实世界,类似于:

  • 要造一台计算机主机,需要CPU、内存卡、显卡、电源等。组装过程也相对复杂,比如一些接线的处理。
  • 要造一辆汽车,需要底盘、方向盘、发动机、座椅、轮子等等。
  • 要造航空母舰,需要甲板、起飞装置、机库、动力系统、武器库等。

复杂对象一般具有这些特点:

  • 整体由多个部件组成。
  • 丰富的组装配置。
  • 复杂的组装流程。

建造者模式,把复杂的对象创建过程进行剥离,尤其是内部部件的配置,进行独立变化和维护。

2. 设计

建造者模式,一般有两种实现。

2.1. 有抽象建造者

有抽象建造者会比较复杂。对建造者进一步抽象,意味着可以有不同的建造者实现。

主要角色有:

  • 抽象建造者(Abstract Builder),声明需要的部件。
  • 具体建造者(Concrete Builder),定义明确的部件。
  • 指挥者(Director),指挥组装过程。
  • 产品(Product),产品,由多个部件组成。

类图如下:

建造者模式——由部分到整体构建对象_第1张图片

产品类,由多个部分组成,不同部分的组合可以视为一个新的产品:

public class Product {

    private String part1;
    private String part2;
    private String part3;

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }

    public void setPart3(String part3) {
        this.part3 = part3;
    }

    @Override
    public String toString() {
        return String.format("产品:[part1=%s, part2=%s, part3=%s]", part1, part2, part3);
    }
}

抽象构建者:

public interface IBuilder {
     void build1();
     void build2();
     void build3();
     Product getResult();
}

具体构建者 A:

public class BuilderA implements IBuilder {

    private Product product;

    public BuilderA() {
        product = new Product();
    }

    public void build1() {
        product.setPart1("A1");
    }

    public void build2() {
        product.setPart2("A2");
    }

    public void build3() {
        product.setPart3("A3");
    }

    public Product getResult() {
        return product;
    }
}

指挥者,指挥构建者生成产品:

public class Director {
    private IBuilder builder;
    public Director(IBuilder builder) {
        this.builder = builder;
    }

    public Product construct() {
        builder.build1();
        builder.build2();
        builder.build3();
        return builder.getResult();
    }
}

调用的地方如下:

public class TestBuilder {

    public static void main(String[] args) {

        // 生成 A 产品
        IBuilder builder = new BuilderA();
        Director director = new Director(builder);
        Product product = director.construct();
        System.out.println(product.toString());

        // 生成 B 产品
        builder = new BuilderB();
        director = new Director(builder);
        product = director.construct();
        System.out.println(product.toString());
    }
}

2.2. 无抽象建造者

无抽象建造者的建造者模式,其实是有抽象建造者的一个退化。

比如只生产一种产品,就没有必要对建造者进行抽象。

无抽象建造者模式很简单,基本角色有:

  • 建造者 Builder,声明产品需要的部件,并且把部件组合成产品
  • 产品 Product,目标产品,由多个部件组成。

对比有抽象建造者的建造者模式,这里的 Builder 其实承担了指挥者的角色。

建造者模式——由部分到整体构建对象_第2张图片

这个模式,可以对产品的部件进行灵活的配置。而且可以使用点语法和链式编程,代码结构清晰。

具体怎样的配置交给外部的使用者进行选择。

大量的开源框架使用 Builder 来进行配置。

3. 应用

建造者模式适用于复杂对象的构造,两种不同建造者有自己的适用范围。

有抽象建造者的:

  • 产品由多个部件组成,不同部件可以更换,不同部件的配置和更换为新的产品。

    比如电脑,同一个品牌的电脑,不同的 CPU 配置和内存配置,可以视为一个新的产品。

  • 部件之间存在复杂的依赖关系。

  • 创建过程中还依赖到外部环境的对象。

    一般由指挥者去依赖外部环境,然后把相关的对象交给建造者去处理。

无抽象建造者的:

  • 产品由多个部件组成
  • 部件之间的依赖关系简单
  • 创建过程中不需要依赖外部环境

在这些应用场景会非常有效:

  • 复杂对象的构建和实现分离,比如客户端的对话框的构建。
  • 多参数方法的改良,如果一个对象的创建需要多个可选参数,而且参数还会变化和扩展,应用建造者模式可以让代码简洁,易与扩展与编写。

3.1. 复杂对象构建举例

一个对话框由多个部件组成,比如标题栏、内容、确定按钮、取消按钮等等。有的页面需要标题栏,有的页面不需要,有的页面需要确定按钮,有的不需要。

这些都通过 Builder 来构建这个复杂的对话框。

CommonAlertDialogFragment builder = new CommonAlertDialogFragment.Builder(getActivity())
	.setMessage(xxxx)
	.setCancelable(true)
	.setNegativeButtonText(xxx, null)
	.setPositiveButtonText(xxx, null)
	.setOnDismissListener(new OnDismissListener() {
		@Override
		public void onDismiss() {
			...
        }
	});
CommonAlertDialogFragment dialog = builder.create()

3.2. 多参数方法改良举例

在方法参数类型较多的时候,我们会使用方法多态,但这样会造成构造方法数量膨胀。

假设我们现在要设计一个下载器,有这么一些配置:

  • 必选项
    • 下载的 url 地址
    • 下载后的文件的保存地址
  • 可选项
    • 是否支持多线程,默认不支持
    • 是否支持文件覆盖,默认可以
    • 下载失败重试次数,默认10次

如果直接用方法多态的方式,如果想要尽可能地覆盖这些条件的组合所有的情况,需要很多的重载方法,于是有这样的代码:

void downloadFile(String url, String savePath);
void downloadFile(String url, String savePath, boolean multiThread);
void downlaodFile(String url, String savePath, boolean multiThread, boolean overwrite);
void downloadFile(String url, String savePath, boolean multiThread, boolean overwrite, int retryNum);
void downloadFile(String url, String savePath, int retryNum);
...

后续增减参数类型,都会修改到这些构造方法,不符合开闭原则。

所以,我们用建造者模式来改善它:

public static class Builder {
	private String url;
	private String savePath;
	private int retryNum = 0;
	private boolean multiThread = false;
	private boolean overwrite = true;

	public Builder(String url, String savePath) {
		this.url = url;
		this.savePath = savePath;
	}

	public Builder addRetryNum(int retryNum) {
		this.retryNum = retryNum;
		return this;
	}

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

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

	public DownloadParams build() {
		return new DownloadParams(url, savePath, retryNum, callBack, multiThread, overwrite, sync);
	}
}

然后方法缩减为只有一个:

void download(DownloadParams params);

使用的地方按需配置。

3.3. JDK:Locale

Locale 有一个通过建造者创建的方式

 Locale aLocale = new Builder()
 	.setLanguage("sr")
 	.setScript("Latn")
 	.setRegion("RS")
 	.build();

3.4. JDK:StringBuilder

String 是不可变字符串,每次操作都会创建新的字符串。

StringBuilder 对其进行了优化,可以通过 append 操作进行字符串拼接,支持非 String 的基本数据类型。

String str = (new StringBuilder())
	.append(1)
	.append(1.2f)
	.append("Hello World!")
	.toString();

3.5. Gson:GsonBuilder

Gson 是 Google 提供的 Gson 解析框架。

Gson 可以通过 GsonBuilder 注册一些自定义的解析器,用来对 Bean 的反射做定制化处理。

GsonBuilder buider = new GsonBuilder().
	.registerTypeAdapter(MediaBean.class, new MediaBeanDeserializer())
	.registerTypeAdapter(UserBean.class, new UserBeanDeserializer())
	.registerTypeAdapter(LiveBean.class, new LiveBeanDeserializer());
Gson gson = builder.create();

3.6. OkHttp:OkHttpClient.Builder

OkHttp 是 Square 公司开源的 Http 客户端,用来进行网络请求,已经支持到 Http/2。

使用这个客户端,需要配置超时时间,是否重定处理,是否允许连接失败重试等。

这里就是使用建造者模式来完成 OkHttp 的配置和实例化的:

OkHttpClient.Builder builder = new OkHttpClient.Builder()
	.connectTimeout(20, TimeUnit.MILLISECONDS)
	.readTimeout(60, TimeUnit.MILLISECONDS)
	.writeTimeout(60, TimeUnit.MILLISECONDS)
	.followRedirects(true);
	.followSslRedirects(true);
	.retryOnConnectionFailure(true);
OkHttpClient okHttpClient = builder.build();

4. 特点

4.1. 优势

  • 屏蔽细节:使用者不需要完全了解建造细节。
  • 易于扩展:不同的建造者相互独立,新的产品可以创建新的建造者。

4.2. 缺点

  • 局限性:产品组成部分必须大部分相同。
  • 类膨胀:如果产品数量过多,也会导致建造者的数量膨胀。

4.3. 注意事项

  • 只有一种产品时,可以使用无抽象建造者,指挥者和建造者在一个类里面实现。
  • 多个产品差异很大的情况下,不推荐使用建造者。

4.4. 和抽象工厂模式的区别

  • 建造者模式返回一个完整产品,抽象工厂返回一套产品
  • 建造者模式侧重构造和组建复杂产品,工厂模式侧重生产不同类型的产品。

你可能感兴趣的:(设计模式,设计模式大全)