`

设计模式 - Builder Pattern[转载]

阅读更多
原文链接:
http://blog.csdn.net/asce1885/article/details/43271531
英文原文:
http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html

我不会详细介绍这个模式,因为已经有大量的文章或者书籍对该模式进行过详细的解析。我将告诉你的是为什么以及什么时候你应该考虑使用它。值得一提的是,我所介绍的这个模式和设计模式四人帮的书(《设计模式:可复用面向对象软件的基础》)里面的有些许区别。四人帮书里面介绍的生成器模式重点在抽象出对象创建的步骤,并通过调用不同的具体实现从而得到不同的结果,本文介绍的生成器模式目的在于移除因多个重载构造函数,可选的参数和setters的过度使用引入的不必要的复杂性。
    如果你的代码中定义了类似下面的拥有大量属性的User类,假设你想把该类定义为不可变的(顺便说一句,除非有足够的理由,否者你应该尽量将变量定义为不可变,我们会在另一篇文章中谈及。)
public class User { 
    private final String firstName;    //required 
    private final String lastName;    //required 
    private final int age;    //optional 
    private final String phone;    //optional 
    private final String address;    //optional 
... 

    现在假设User类的某些属性是必须的,某些属性是可选的,那么你会如何构建这样一个类的实例呢?由于所有属性都声明为final,所以你需要在构造函数中把它们都赋值,但你又想让该类的使用者在实例化时可以忽略可选的参数。
    最简单有效的方法是定义一个只接收必选参数的构造函数,一个接收所有必选参数和一个可选参数的构造函数,一个接收所有必选参数和两个可选参数的构造函数,依次类推。这样的代码看起来是怎样的呢?大概如下所示:
public User(String firstName, String lastName) { 
    this(firstName, lastName, 0); 

 
public User(String firstName, String lastName, int age) { 
    this(firstName, lastName, age, ''); 

 
public User(String firstName, String lastName, int age, String phone) { 
    this(firstName, lastName, age, phone, ''); 

 
public User(String firstName, String lastName, int age, String phone, String address) { 
    this.firstName = firstName; 
    this.lastName = lastName; 
    this.age = age; 
    this.phone = phone; 
    this.address = address; 

    好消息是这样创建类实例的方式是可行的。然而这种方法存在的问题是相当明显的。当类只有少量几个属性时,这种方式也没什么大碍,但随着类属性个数的增加,代码将变得越来越难以阅读和维护。更严重的是,对调用者而言,代码也变得更加难以使用。作为调用者,我应该使用哪个构造函数呢?是两个参数的构造函数还是三个参数的呢?如果我不显示指定可选参数的值,那它的默认值是多少呢?如果我只要设置地址而不想设置年龄和手机号码呢?在这种情况下,我需要调用具有指定参数的构造函数,并传递一个默认值给那些我不感兴趣的参数。另外,当几个参数具有相同类型时,也很容易混淆调用者的使用,第一个String类型的参数是指的手机号码还是地址呢?
    那么对以上这些情况我们有其他的选择吗?我们可以遵循JavaBeans规范,定义一个默认无参构造函数,并对每个属性提供setters和getters函数,如下面所示:
public class User { 
    private String firstName; // required 
    private String lastName; // required 
    private int age; // optional 
    private String phone; // optional 
    private String address;  //optional 
 
    public String getFirstName() { 
        return firstName; 
    } 
    public void setFirstName(String firstName) { 
        this.firstName = firstName; 
    } 
    public String getLastName() { 
        return lastName; 
    } 
    public void setLastName(String lastName) { 
        this.lastName = lastName; 
    } 
    public int getAge() { 
        return age; 
    } 
    public void setAge(int age) { 
        this.age = age; 
    } 
    public String getPhone() { 
        return phone; 
    } 
    public void setPhone(String phone) { 
        this.phone = phone; 
    } 
    public String getAddress() { 
        return address; 
    } 
    public void setAddress(String address) { 
        this.address = address; 
    } 

    上面这种方法貌似易于阅读和维护。作为使用者,我可以创建一个空实例,并只设置我感兴趣的属性值。哪里出错了吗?这种解决方案有两个主要的问题。第一个问题是使得该类的实例具有不连续的状态。如果你想创建一个同时具有五个属性值的类实例,那么直到所有属性值的设置函数setX被调用了,该实例才具有完整的状态。这意味着调用者应用程序的某些模块可能会看到这个实例的不完整的状态。第二个缺点是这种方案使得User类是可变的,你会因此而失去很多不可变对象的好处。
    幸运的是,我们有第三种选择:生成器模式。这种解决方案类似下面代码所示:
public class User { 
    private final String firstName; // required 
    private final String lastName; // required 
    private final int age; // optional 
    private final String phone; // optional 
    private final String address; // optional 
 
    private User(UserBuilder builder) { 
        this.firstName = builder.firstName; 
        this.lastName = builder.lastName; 
        this.age = builder.age; 
        this.phone = builder.phone; 
        this.address = builder.address; 
    } 
 
    public String getFirstName() { 
        return firstName; 
    } 
 
    public String getLastName() { 
        return lastName; 
    } 
 
    public int getAge() { 
        return age; 
    } 
 
    public String getPhone() { 
        return phone; 
    } 
 
    public String getAddress() { 
        return address; 
    } 
 
    public static class UserBuilder { 
        private final String firstName; 
        private final String lastName; 
        private int age; 
        private String phone; 
        private String address; 
 
        public UserBuilder(String firstName, String lastName) { 
            this.firstName = firstName; 
            this.lastName = lastName; 
        } 
 
        public UserBuilder age(int age) { 
            this.age = age; 
            return this; 
        } 
 
        public UserBuilder phone(String phone) { 
            this.phone = phone; 
            return this; 
        } 
 
        public UserBuilder address(String address) { 
            this.address = address; 
            return this; 
        } 
 
        public User build() { 
            return new User(this); 
        } 
 
    } 

    有几个关键点需要注意一下:
1)User类的构造函数是私有的,这意味着调用者不能直接实例化这个类;
2)这个类再次成为不可变的,所有必选的属性值都是final的并且在构造函数中设置。另外,对属性值我们只提供getters函数;
3)该模式使用Fluent Interface惯用法(参见http://martinfowler.com/bliki/FluentInterface.html)使得调用者的代码更加可读(一会儿我们会看到这样的例子);
4)该模式的构造函数只接收必选的属性值作为参数,也只有这些必选的属性值被设置为final,以此保证它们在构造函数中设置;
    生成器模式具有上面介绍过的其他两个方案的优点,同时又没有它们的缺点。调用者的代码更容易编写,更重要的是,更容易阅读。我听过的对该模式唯一的批评是你必须在builder内部类中重复外部类的属性定义。然而,鉴于builder类通常是它所构建的类的静态内部类,它们可以很容易同步的修改。
现在,试图创建User实例的调用者代码看起来是怎样的呢?如下所示:
public User getUser() { 
    return new 
            User.UserBuilder('Jhon', 'Doe') 
            .age(30) 
            .phone('1234567') 
            .address('Fake address 1234') 
            .build(); 

    相当简洁,不是吗?你可以使用一行代码就创建User实例,更重要的是,代码易于阅读。而且你可以保证任何时候获取的User实例都处于完整的状态。这个模式相当灵活,一个builder可以在调用build函数之前通过设置不同的属性值来创建不同的类实例。builder甚至可以在每次调用之间自动补全生成的字段,例如id值或者序列号等。重要的一点是,类似构造函数,builder能够使得其参数为不可变的。build函数能够检查这些不可变参数并在参数无效时抛出IllegalStateException异常。
    参数是从builder类拷贝到外部类,并在外部类而不是builder类中进行有效性校验的,这一点至关重要。原因在于builder类不是线程安全的,如果我们在创建外部类对象之前检查参数有效性,那么在参数校验和参数被拷贝到外部类的时间段之间,这些参数的值可能被另一个线程所更改。这个时间段就是著名的“脆弱之时”(“window of vulnerability”,脆弱之时,尤指冷战时期,美国的陆基导弹很容易成为苏联首次攻击目标的论点)。在我们的User例子中,代码类似下面所示:
public User build() { 
    User user = new user(this); 
    if (user.getAge() < 120) { 
        throw new IllegalStateException(“Age out of range”); // thread-safe 
    } 
    return user; 

    上面的版本是线程安全的,因为我们先创建了User的实例,然后才对User不可变实例中不可变量进行校验。而下面的代码看起来实现相同的功能,但却是非线程安全的,因此我们要避免使用这样的方式:
public User build() { 
    if (age < 120) { 
        throw new IllegalStateException(“Age out of range”); // bad, not thread-safe 
    } 
    // This is the window of opportunity for a second thread to modify the value of age 
    return new User(this); 

    该模式最后一个好处是builder可以传递给另外一个函数,使得该函数可以为调用者创建一个或者多个对象实例,而不需要知道对象实例的具体创建细节。为了达到这个目的,我们通常会定义一个简单的接口如下所示:
public interface Builder<T extends User> { 
    T build(); 

    在前面的User例子中,UserBuilder类需要改为实现Builder<User>接口,这样一来,build函数类似如下所示:
UserCollection buildUserCollection(Builder<? extends User> userBuilder){...} 
    好吧,这是我写过的第一篇长博客,总结一下,生成器模式对于具有多于几个参数(并不精准,通常对于具有4个或者以上属性的类我会使用这个模式)的类的构造是个很好的选择,特别是当这些属性多数是可选的时候。应用这个模式可以使得调用者代码易于阅读,编写和维护。此外,由于你的类是不可变的你的代码将更加安全。
   更新:如果你使用Eclipse作为你的IDE,那么你可以使用不少插件来简化该模式引入的样板代码的编写。我知道的三个插件如下:
1)http://code.google.com/p/bpep/
2)http://code.google.com/a/eclipselabs.org/p/bob-the-builder/
3)http://code.google.com/p/fluent-builders-generator-eclipse-plugin/
我自己没有试用过这些插件,所以不能下结论说哪一个更好用。我想其他IDE也存在类似的插件。
分享到:
评论

相关推荐

    C#设计模式-吕震宇

    C#设计模式(8)-Builder Pattern C#设计模式(7)-Singleton Pattern C#设计模式(6)-Abstract Factory Pattern C#设计模式(5)-Factory Method Pattern C#设计模式(4)-Simple Factory Pattern C#设计模式...

    design-pattern-java.pdf

    实现对象的复用——享元模式(二) 实现对象的复用——享元模式(三) 实现对象的复用——享元模式(四) 实现对象的复用——享元模式(五) 代理模式-Proxy Pattern 设计模式之代理模式(一) 设计模式之代理模式...

    CoreJava-DesignPattern

    创意设计模式 -- Abstract Factory - Done -- Builder - Done -- Factory Method -- Object Pool -- Prototype - Done -- Singleton - Done 结构设计模式 -- Adapter -- Bridge -- Composite -- Decorator - Done ...

    36种最新设计模式整理

    36种最新设计模式整理 Design Pattern: Simple Factory 模式 Design Pattern: Abstract Factory 模式 Design Pattern: Builder 模式 Design Pattern: Factory Method 模式 Design Pattern: Prototype 模式 ...

    java设计模式源码-DesignPattern:设计模式(Java实现源码)

    建造者模式(builderPattern) 原型模式(prototypePattern) 适配器模式(adapterPattern) 桥接模式(bridgePattern) 过滤器模式(filterPattern) 组合模式(compositePattern) 装饰器模式(decoratorPattern) 外观模式...

    C#设计模式.PDF

    C#设计模式(8)-Builder Pattern 57 一、 建造者(Builder)模式 57 二、 Builder模式的结构: 58 三、 程序举例: 58 四、 建造者模式的活动序列: 62 五、 建造者模式的实现: 62 六、 建造者模式的演化 68 七、...

    C#设计模式大全

    C#设计模式(8)-Builder Pattern 一、 建造者(Builder)模式 二、 Builder模式的结构: 三、 程序举例: 四、 建造者模式的活动序列: 五、 建造者模式的实现: 六、 建造者模式的演化 七、 在什么情况下...

    设计模式面面观(10):桥接模式(Bridge Pattern)-结构型模式

    创建型模式 (100%) 设计模式面面观(5):抽象工厂模式(AbstractFactory)-创建型模式 (100%) 设计模式面面观(6):生成器模式(Builder)-创建型模式 (100%) 设计模式面面观(7):原型模式(Prototype...

    C++设计模式(Design Pattern)范例源代码

    23种设计模式(Design Pattern)的C++实现范例,包括下面列出的各种模式,代码包含较详细注释。另外附上“设计模式迷你手册.chm” 供参考。 注:项目在 VS2008 下使用。 创建型: 抽象工厂模式(Abstract Factory) ...

    用Java实现23种设计模式

    用Java实现23种设计模式 1. 创建型模式 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)...

    33种JAVA设计模式DEMO

    这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern) 抽象工厂模式...

    [创建型模式]设计模式之建造者模式(Builder Pattern)

    NULL 博文链接:https://jacky-dai.iteye.com/blog/2295399

    design-pattern-test:设计模式学习

    design-pattern-test 设计模式学习练习 模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns) 序号 模式&描述 包括 1 创建型模式---...

    设计模式PPT

    创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:  工厂方法模式(Factory Method Pattern)  抽象工厂模式(Abstract Factory Pattern)  建造者模式(Builder Pattern)  原型模式...

    C#版 24种设计模式

    工厂方法模式(Factory Method Pattern) 观察者模式(Observer Pattern) 建造者模式(Builder Pattern) 解释器模式(Interpreter Pattern) 命令模式(Command Pattern) 模板方法模式(Template Method Pattern) 桥接模式...

    单例模式源码java-DesignPattern:在个人自学阶段的23种设计模式代码的全部实现,全部使用Java编写,其中还包括各个设计模式在

    在个人自学阶段的23种设计模式代码的全部实现,全部使用Java编写,其中还包括各个设计模式在源码中的使用,每种设计模式都举了一个简单的小例子来进行实现,并加以注释 包名解释 一、DesignPattern 1.1 创建型模式 ...

    BuilderPattern.unitypackage

    BuilderPattern.unitypackage是一个建造者模式的例子。

    Java24种设计模式,Java24种设计模式,24种设计模式,学会了这24种设计模式,可以打遍天下无敌手,设计模式非常重要

    10、建造者模式BUILDER PATTERN 11、桥梁模式BRIDGE PATTERN 12、命令模式COMMAND PATTERN 13、装饰模式DECORATOR PATTERN 14、迭代器模式ITERATOR PATTERN 15、组合模式COMPOSITE PATTERN 16、观察者模式...

    uu-design-pattern:23种设计模式案例

    23种设计模式演示代码文件结构图gof23 |- creational(创建型模式) |- simplefactory 简单工厂模式案例 |- factorymethod 工厂方法模式案例 |- abstractfactory 抽象工厂模式案例 |- builder 建造者模式案例 |- ...

Global site tag (gtag.js) - Google Analytics