设计模式 - Builder模式

来源:互联网 发布:java 防重设计 编辑:程序博客网 时间:2024/04/29 10:24

建造者模式(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(第二版)

0 0