`

设计模式 - 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

    "Design Pattern"这个压缩包文件很可能包含了一些关于设计模式的实例和文档,特别是提到了"design-pattern\doc\api"目录下的"index.html",这可能是一个交互式的文档或者教程,通过实例帮助开发者更好地理解设计模式...

    design-pattern-java.pdf

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

    java设计模式---诙谐易懂版

    代理模式(Proxy Pattern)、单例模式(Singleton Pattern)、工厂方法模式(...Builder Pattern)、桥梁模式(Bridge Pattern)、命令模式(Command Pattern)、装饰模式(Decorator Pattern)、迭代器模式(Iterator ...

    面试-Java一些常见面试题+题解之设计模式-DesignPattern.zip

    在Java面试中,设计模式是不可或缺的一部分,它们是软件工程中的最佳实践,为解决常见的编程问题提供了可复用的解决方案。本资料集包含了Java常见面试题及设计模式的解析,帮助求职者准备面试,提升技术素养。以下是...

    C++11全套设计模式-23种指针的用法(a full DesignPattern -DesignPattern.zip

    本资料包“DesignPattern - DesignPattern.zip”提供了对C++11中23种设计模式的全面讲解,特别是结合指针使用的部分,以下是对这些知识点的详细阐述: 1. **单例模式(Singleton)**:确保一个类只有一个实例,并...

    CoreJava-DesignPattern

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

    DP-Builder-pattern

    构建器模式是一种创建型设计模式,它提供了一种方法来分步骤构造复杂的对象,使得构建过程和表示细节能够分离。这种模式在实际编程中,尤其是在对象的创建过程中涉及多种组装步骤时,非常有用。 在"DP-Builder-...

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

    【创建型模式】设计模式之建造者模式(Builder Pattern) 建造者模式(Builder Pattern)是设计模式中的一个创建型模式,它提供了一种方法来分步骤构造复杂的对象,使得构造过程和表示分离,使得同样的构建过程可以...

    JAVA设计模式-chm版

    Java设计模式是软件开发中的一种最佳实践,它总结了在解决特定问题时程序员们经常采用的有效方法。这个“JAVA设计模式-chm版”资源显然包含了关于Java设计模式的详细信息,便于理解和应用。设计模式是对常见问题的...

    设计模式整理代码-pattern.zip

    这里我们关注的是一个名为"pattern.zip"的压缩包文件,它包含了23种经典的设计模式,这些模式在实践中被广泛使用,提高了代码的可读性、可维护性和可扩展性。这篇文章将详细探讨这些设计模式及其应用。 首先,23种...

    JAVA设计模式-设计模式公司出品

    8. 建造者模式(BUILDERPATTERN):建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 此外,书中还提到了“六大设计原则”,这是设计模式背后的指导思想,包括: 1. 单一...

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

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

    设计模式之建造者Builder模式

    **建造者模式(Builder Pattern)**是软件设计模式中的一种,属于创建型模式。它将复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常用于那些需要大量构造参数的对象,通过...

    设计模式课程设计---使用5个以上不同的设计模式完成(java)

    在本设计模式课程设计中,我们重点探讨了五个核心的设计模式:原型模式、单例模式、抽象工厂模式、代理模式和建造者模式。这些模式在Java编程中具有广泛的应用,能够帮助开发者创建更加灵活、可扩展和易于维护的代码...

    建造者模式【Builder Pattern】(一)问题提出

    在“BuilderPattern”这个压缩包中,可能包含了关于建造者模式的示例代码、解释文档或教程。这些资源可以帮助我们更好地理解建造者模式的实现和应用场景。通过研究这些内容,我们可以学习如何设计和使用建造者模式,...

    c++设计模式-创建型模式-建造者模式

    **建造者模式(Builder Pattern)**是软件设计模式中的一种创建型模式,它允许我们分步骤构建复杂的对象,而无需暴露构建过程。在C++中,这种模式常用于将对象的创建过程与使用过程分离,使得对象的构建更加灵活,...

    设计模式实现(Java、C++、Golang)-DesignPattern.zip

    设计模式是软件工程中的一种最佳实践,它是在特定上下文中解决常见问题的模板或蓝图,可以被反复使用,以提高代码的可读性、可维护性和可复用性。本资料包“DesignPattern.zip”包含了Java、C++和Golang三种编程语言...

    java与设计模式--通俗易懂的介绍

    Java 与设计模式是软件开发中的重要主题,它们旨在提高代码的可维护性、灵活性和重用性。设计模式是对在软件设计中经常出现的问题及其解决方案的一种通用、可复用的描述。本篇将通俗易懂地介绍几种常见的设计模式,...

    GOF-设计模式-Design Patterns-英文原版-高清-有目录-有页码

    ### GOF设计模式详解 #### 一、设计模式概述 **设计模式**(Design Patterns)是软件工程中的一个重要概念,它是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。本章节将从多个角度对设计模式...

Global site tag (gtag.js) - Google Analytics