设计模式 - Builder模式

Posted demonyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式 - Builder模式相关的知识,希望对你有一定的参考价值。

建造者模式(Builder Pattern)又叫生成器模式,属于对象创建类模式。建造者模式是一个运用比较广泛的设计模式,经常可以在开源库中看到它的身影,自己重构代码时也可以灵活运用它。

基本概念

建造者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以产生不同的表示,其通用类图如下所示:

由上图可知,建造者模式包含以下 4 种角色:
1. Product 产品类
表示被建造者创建的复杂产品对象。
2. Builder 抽象建造类
为创建 Product 对象的各个部件指定抽象接口。
3. ConcreteBuilder 具体建造类
实现抽象构建类的抽象方法,返回一个构建好的对象。
4. Director 导演类
构造一个使用 Builder 接口的对象。

建造者模式的通用源码如下所示:

/**
 * 产品类
 */
public class Product 
    public void doSomething() 
        // 产品业务逻辑
    


/**
 * 抽象建造类
 */
public abstract class Builder 
    // 设置产品的不同部分
    public abstract void setPart();

    // 建造产品
    public abstract Product buildProduct();


/**
 * 具体建造类
 */
public class ConcreteProduct extends Builder 
    private Product product = new Product();

    @Override
    public void setPart() 
        // 产品类内的逻辑处理
    

    @Override
    public Product buildProduct() 
        return product;
    


/**
 * 导演类
 */
public class Director 
    private Builder builder = new ConcreteProduct();

    // 建造一个产品
    public Product getProductA() 
        // 设置不同的零件
        builder.setPart();
        return builder.buildProduct();
    

建造者模式的优点

1. 封装性:使用建造者模式可以使客户端不需要知道产品内部的组成细节。
2. 开闭原则:建造者独立,容易扩展。
3. 便于对构造过程进行更精细的控制:由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不会对其他模块产生影响。

建造者模式使用场景

1. 相同的方法,不同的执行顺序,产生不同的事件结果。
2. 对象创建过程复杂,初始化参数很多或者调用顺序不同会产生不同的效能。

建造者模式应用示例

在创建对象时,静态工厂方法和构造器有一个共同的局限性:他们都不能很好的扩展到大量的可选参数。以下借用 Effective Java 中的示例。用一个类表示包装食品外面显示的营养成分标签,这些标签有一些域是必需的,比如含量以及卡路里;还有一些域是可选的,比如总脂肪量,饱和脂肪量,转化脂肪,胆固醇等。
对于这样的类,一般习惯采用重叠构造器(telescoping constructor)模式,提供一个只有必要参数的构造器,提供第二个构造器包含一个可选参数,第三个构造器包含两个可选参数,依此类推。以下是示例代码,它只显示 3 个可选参数:

public class NutritionFacts 
    private final int servingSize;    // (mL)            required
    private final int servings;       // (per container) required
    private final int calories;       //                 optional
    private final int fat;            // (g)             optional
    private final int sodium;         // (mg)            optional

    public NutritionFacts(int servingSize, int servings) 
        this(servingSize, servings, 0);
    

    // 一个可选参数
    public NutritionFacts(int servingSize, int servings, int calories) 
        this(servingSize, servings, calories, 0);
    

    // 二个可选参数
    public NutritionFacts(int servingSize, int servings, 
            int calories, int fat) 
        this(servingSize, servings, calories, fat, 0);
    

    // 三个可选参数
    public NutritionFacts(int servingSize, int servings, 
            int calories, int fat, int sodium) 
        this.servingSize = servingSize;
        this.servings    = servings;
        this.calories    = calories;
        this.fat         = fat;
        this.sodium      = sodium;
    

如果仅仅只有 5 个参数还不算太糟,但随着参数数目的增加,重叠构造器也会增加,导致客户端代码会很难编写,并且代码也难以阅读。如果客户端不小心颠倒了其中两个参数的顺序,在编译器不会报错的情况下,程序会在运行时出现错误行为。
这种情况下,可以使用建造者模式代替重叠构造器。客户端不直接生成产品对象,而是先得到一个 builder 对象,然后在 builder 对象上调用类似于 setter 的方法设置每个相关的可选参数。最后客户端调用无参的 build 方法来生成产品对象。以下是示例代码:

public class NutritionFacts 
    private final int servingSize;    // (mL)            required
    private final int servings;       // (per container) required
    private final int calories;       //                 optional
    private final int fat;            // (g)             optional
    private final int sodium;         // (mg)            optional

    public static class Builder 
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters
        private int calories = 0;
        private int fat      = 0;
        private int sodium   = 0;

        public Builder(int servingSize, int servings) 
            this.servingSize = servingSize;
            this.servings    = servings;
        

        public Builder calories(int val) 
            this.calories = val;
            return this;
        

        public Builder fat(int val) 
            this.fat = val;
            return this;
        

        public Builder sodium(int val) 
            this.sodium = val;
            return this;
        

        public NutritionFacts build() 
            return new NutritionFacts(this);
        
    

    public NutritionFacts(Builder builder) 
        this.servingSize = builder.servingSize;
        this.servings    = builder.servings;
        this.calories    = builder.calories;
        this.fat         = builder.fat;
        this.sodium      = builder.sodium;
    

在建造者模式通用类图的基础上去掉了抽象建造类,且将具体建造类至于产品类内部。

很明显,客户端代码更容易编写,也更容易阅读。下面是客户端代码:

    NutritionFacts cocaCola = new NutritionFacts.Builder(100, 200).calories(300)
            .fat(400).sodium(500).build();

由此可知,Builder 模式十分灵活,builder 对象利用单独的方法来设置每个参数,同时还可以对参数增加约束条件。builder 的参数可以在创建对象期间灵活调整,也可以随着不同的对象而变化。

android 应用程序中也经常用到建造者模式,其中 AlertDialog 对话框的创建最为典型。由于 AlertDialog 有很多组成部分,比如 icon, message, title, button, view 等等,所以用重叠构造器也会遇到同样的问题。通过分析源码可知,AlertDialog 类也存在一个 Builder 内部类,结构和上面示例一样一样的。以下是 AlertDialog 的一个使用场景:

    new AlertDialog.Builder(MainActivity.this)
            .setTitle("Warning")
            .setMessage("Wifi Disable!")
            .setIcon(R.drawable.wifi_disable)
            .setPositiveButton("OK", new DialogInterface.OnClickListener() 
                @Override
                public void onClick(DialogInterface dialog, int which) 
                    dialog.dismiss();
                
            )
            .setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                @Override
                public void onClick(DialogInterface dialog, int which) 
                    dialog.dismiss();
                
            )
            .show();

上述链式调用如果太长其实也影响代码阅读,最好做适当的封装,并且分步初始化。

此外,StrictMode 类中 ThreadPolicy 对象以及 VmPolicy 对象的创建,也采用同样的方式。

与工厂方法模式区别

建造者模式和工厂方法模式都属于对象创建类模式,但它们之间有比较明显的区别:在工厂方法模式里,关注的是一个产品整体,无须关心产品的各部分是如何创建出来的;而在建造者模式中,需要关注具体产品各个部件的创建以及装配顺序。因此,可以说工厂方法模式是一种粗线条的对象创建模式,而建造者模式关注产品各部分的创建过程,是一种细线条的对象创建模式。

参考资料

1. 设计模式之禅
2. Effective Java(第二版)

以上是关于设计模式 - Builder模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式学习总结创建者模式(Builder)

Android 设计模式 笔记 - Builder模式

设计模式解密 - 建造者模式

设计模式 | 建造者模式/生成器模式(builder)

Java设计模式总结

构建器模式