Java设计模式04:常用设计模式之建造者模式(创建型模式)
1. Java之建造者模式(Builder Pattern)
(1)概念:
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 [ 构建与表示分离, 同构建不同表示 ]
与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。
建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。
举个简单的例子,如汽车,有很多部件,车轮,方向盘,发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一部汽车,这个装配过程也很复杂(需要很好的组装技术), builder模式就是为了将部件和组装分开。
(2)演示案例代码示例:
我们通过一个例子来引出Builder模式。假设有一个Person类,我们通过该Person类来构建一大批人,这个Person类里有很多属性,最常见的比如name,age,weight,height等等,并且我们允许这些值不被设置,也就是允许为null,该类的定义如下:
1 public class Person { 2 private String name; 3 private int age; 4 private double height; 5 private double weight; 6 7 public String getName() { 8 return name; 9 } 10 11 public void setName(String name) { 12 this.name = name; 13 } 14 15 public int getAge() { 16 return age; 17 } 18 19 public void setAge(int age) { 20 this.age = age; 21 } 22 23 public double getHeight() { 24 return height; 25 } 26 27 public void setHeight(double height) { 28 this.height = height; 29 } 30 31 public double getWeight() { 32 return weight; 33 } 34 35 public void setWeight(double weight) { 36 this.weight = weight; 37 } 38 }
然后我们为了方便可能会定义一个构造方法:
1 public Person(String name, int age, double height, double weight) { 2 this.name = name; 3 this.age = age; 4 this.height = height; 5 this.weight = weight; 6 }
或许为了方便new对象,你还会定义一个空的构造方法:
1 public Person() { 2 }
甚至有时候你很懒,只想传部分参数,你还会定义如下类似的构造方法:
1 public Person(String name) { 2 this.name = name; 3 } 4 5 public Person(String name, int age) { 6 this.name = name; 7 this.age = age; 8 } 9 10 public Person(String name, int age, double height) { 11 this.name = name; 12 this.age = age; 13 this.height = height; 14 }
于是你就可以这样创建各个需要的对象:
1 Person p1=new Person(); 2 Person p2=new Person("张三"); 3 Person p3=new Person("李四",18); 4 Person p4=new Person("王五",21,180); 5 Person p5=new Person("赵六",17,170,65.4);
可以想象一下这样创建的坏处,最直观的就是四个参数的构造函数的最后面的两个参数到底是什么意思,可读性不怎么好,如果不点击看源码,鬼知道哪个是weight哪个是height。还有一个问题就是当有很多参数时,编写这个构造函数就会显得异常麻烦,这时候如果换一个角度,试试Builder模式,你会发现代码的可读性一下子就上去了。
我们给Person增加一个静态内部类Builder类,并修改Person类的构造函数,代码如下:
1 public class Person { 2 private String name; 3 private int age; 4 private double height; 5 private double weight; 6 7 private Person(Builder builder) { 8 this.name=builder.name; 9 this.age=builder.age; 10 this.height=builder.height; 11 this.weight=builder.weight; 12 } 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public int getAge() { 22 return age; 23 } 24 25 public void setAge(int age) { 26 this.age = age; 27 } 28 29 public double getHeight() { 30 return height; 31 } 32 33 public void setHeight(double height) { 34 this.height = height; 35 } 36 37 public double getWeight() { 38 return weight; 39 } 40 41 public void setWeight(double weight) { 42 this.weight = weight; 43 } 44 45 static class Builder{ 46 private String name; 47 private int age; 48 private double height; 49 private double weight; 50 public Builder name(String name){ 51 this.name=name; 52 return this; 53 } 54 public Builder age(int age){ 55 this.age=age; 56 return this; 57 } 58 public Builder height(double height){ 59 this.height=height; 60 return this; 61 } 62 63 public Builder weight(double weight){ 64 this.weight=weight; 65 return this; 66 } 67 68 public Person build(){ 69 return new Person(this); 70 } 71 } 72 }
从上面的代码中我们可以看到,我们在Builder类里定义了一份与Person类一模一样的变量,通过一系列的成员函数进行设置属性值,但是返回值都是this,也就是都是Builder对象,最后提供了一个build函数用于创建Person对象,返回的是Person对象,对应的构造函数在Person类中进行定义,也就是构造函数的入参是Builder对象,然后依次对自己的成员变量进行赋值,对应的值都是Builder对象中的值。此外Builder类中的成员函数返回Builder对象自身的另一个作用就是让它支持链式调用,使代码可读性大大增强。
于是我们就可以这样创建Person类:
1 Person.Builder builder=new Person.Builder(); 2 Person person=builder 3 .name("张三") 4 .age(18) 5 .height(178.5) 6 .weight(67.4) 7 .build();
有没有觉得创建过程一下子就变得那么清晰了。对应的值是什么属性一目了然,可读性大大增强。
(3)Builder模式在Android中使用:
其实在Android中, Builder模式也是被大量的运用。
• 比如常见的对话框的创建:
1 AlertDialog.Builder builder=new AlertDialog.Builder(this); 2 AlertDialog dialog=builder.setTitle("标题") 3 .setIcon(android.R.drawable.ic_dialog_alert) 4 .setView(R.layout.myview) 5 .setPositiveButton(R.string.positive, new DialogInterface.OnClickListener() { 6 @Override 7 public void onClick(DialogInterface dialog, int which) { 8 9 } 10 }) 11 .setNegativeButton(R.string.negative, new DialogInterface.OnClickListener() { 12 @Override 13 public void onClick(DialogInterface dialog, int which) { 14 15 } 16 }) 17 .create(); 18 dialog.show();
• 其实在java中有两个常见的类也是Builder模式,那就是StringBuilder和StringBuffer,只不过其实现过程简化了一点罢了。
我们再找找Builder模式在各个框架中的应用:
• 如Gson中的GsonBuilder,代码太长了,就不贴了,有兴趣自己去看源码,这里只贴出其Builder的使用方法:
1 GsonBuilder builder=new GsonBuilder(); 2 Gson gson=builder.setPrettyPrinting() 3 .disableHtmlEscaping() 4 .generateNonExecutableJson() 5 .serializeNulls() 6 .create();
• 再看看著名的网络请求框架OkHttp:
1 Request.Builder builder=new Request.Builder(); 2 Request request=builder.addHeader("","") 3 .url("") 4 .post(body) 5 .build();
除了Request外,Response也是通过Builder模式创建的。贴一下Response的构造函数:
1 private Response(Builder builder) { 2 this.request = builder.request; 3 this.protocol = builder.protocol; 4 this.code = builder.code; 5 this.message = builder.message; 6 this.handshake = builder.handshake; 7 this.headers = builder.headers.build(); 8 this.body = builder.body; 9 this.networkResponse = builder.networkResponse; 10 this.cacheResponse = builder.cacheResponse; 11 this.priorResponse = builder.priorResponse; 12 }
可见各大框架中大量的运用了Builder模式。最后总结一下:
- 定义一个静态内部类Builder,内部的成员变量和外部类一样。
- Builder类通过一系列的方法用于成员变量的赋值,并返回当前对象本身(this)。
- Builder类提供一个build方法或者create方法用于创建对应的外部类,该方法内部调用了外部类的一个私有构造函数,该构造函数的参数就是内部类Builder。
- 外部类提供一个私有构造函数供内部类调用,在该构造函数中完成成员变量的赋值,取值为Builder对象中对应的值。
2. Java之建造者模式应用场景(JavaMail)
使用场景:
假设有一个电子杂志系统,定期地向用户的电子邮件信箱发送电子杂志。用户可以通过网页订阅电子杂志,也可以通过网页结束订阅。当客户开始订阅时,系统发送一个电子邮件表示欢迎,当客户结束订阅时,系统发送一个电子邮件表示欢送。本例子就是这个系统负责发送“欢迎”和“欢送”邮件的模块。
这个系统含有客户端(Client)、导演者(Director)、抽象建造者(Builder)、具体建造者(WelcomeBuilder和GoodbyeBuilder)、产品(WelcomeMessage和GoodbyeMessage)等角色。
源代码
抽象类AutoMessage源代码,send()操作仅仅是示意性的,并没有给出任何发送电子邮件的代码。
(1)抽象类AutoMessage:
1 public abstract class AutoMessage { 2 //收件人地址 3 private String to; 4 //发件人地址 5 private String from; 6 //标题 7 private String subject; 8 //内容 9 private String body; 10 //发送日期 11 private Date sendDate; 12 public void send(){ 13 System.out.println("收件人地址:" + to); 14 System.out.println("发件人地址:" + from); 15 System.out.println("标题:" + subject); 16 System.out.println("内容:" + body); 17 System.out.println("发送日期:" + sendDate); 18 } 19 public String getTo() { 20 return to; 21 } 22 public void setTo(String to) { 23 this.to = to; 24 } 25 public String getFrom() { 26 return from; 27 } 28 public void setFrom(String from) { 29 this.from = from; 30 } 31 public String getSubject() { 32 return subject; 33 } 34 public void setSubject(String subject) { 35 this.subject = subject; 36 } 37 public String getBody() { 38 return body; 39 } 40 public void setBody(String body) { 41 this.body = body; 42 } 43 public Date getSendDate() { 44 return sendDate; 45 } 46 public void setSendDate(Date sendDate) { 47 this.sendDate = sendDate; 48 } 49 50 }
(2)具体产品类WelcomeMessage:
1 public class WelcomeMessage extends AutoMessage { 2 /** 3 * 构造子 4 */ 5 public WelcomeMessage(){ 6 System.out.println("发送欢迎信息"); 7 } 8 }
(3)具体产品类GoodbyeMessage:
1 public class GoodbyeMessage extends AutoMessage{ 2 /** 3 * 构造子 4 */ 5 public GoodbyeMessage(){ 6 System.out.println("发送欢送信息"); 7 } 8 }
(4)抽象建造者类
1 public abstract class Builder { 2 protected AutoMessage msg; 3 4 //标题零件的建造方法 5 public abstract void buildSubject(); 6 7 //内容零件的建造方法 8 public abstract void buildBody(); 9 10 //收件人零件的建造方法 11 public void buildTo(String to){ 12 msg.setTo(to); 13 } 14 15 //发件人零件的建造方法 16 public void buildFrom(String from){ 17 msg.setFrom(from); 18 } 19 20 //发送时间零件的建造方法 21 public void buildSendDate(){ 22 msg.setSendDate(new Date()); 23 } 24 25 /** 26 * 邮件产品完成后,用此方法发送邮件 27 * 此方法相当于产品返还方法 28 */ 29 public void sendMessage(){ 30 msg.send(); 31 } 32 }
(5)具体建造者WelcomeBuilder
1 public class WelcomeBuilder extends Builder { 2 public WelcomeBuilder(){ 3 msg = new WelcomeMessage(); 4 } 5 6 @Override 7 public void buildBody() { 8 // TODO Auto-generated method stub 9 msg.setBody("欢迎内容"); 10 } 11 12 @Override 13 public void buildSubject() { 14 // TODO Auto-generated method stub 15 msg.setSubject("欢迎标题"); 16 } 17 }
(6)具体建造者GoodbyeBuilder
1 public class GoodbyeBuilder extends Builder { 2 public GoodbyeBuilder(){ 3 msg = new GoodbyeMessage(); 4 } 5 @Override 6 public void buildBody() { 7 // TODO Auto-generated method stub 8 msg.setBody("欢送内容"); 9 } 10 @Override 11 public void buildSubject() { 12 // TODO Auto-generated method stub 13 msg.setSubject("欢送标题"); 14 } 15 }
(7)导演者Director,这个类提供一个construct()方法,此方法调用建造者的建造方法,包括buildTo()、buildFrom()、buildSubject()、buildBody()、buildSendDate()等,从而一部分一部分地建造出产品对象,既AutoMessage对象。
1 public class Director { 2 Builder builder; 3 /** 4 * 构造子 5 */ 6 public Director(Builder builder){ 7 this.builder = builder; 8 } 9 /** 10 * 产品构造方法,负责调用各零件的建造方法 11 */ 12 public void construct(String toAddress , String fromAddress){ 13 this.builder.buildTo(toAddress); 14 this.builder.buildFrom(fromAddress); 15 this.builder.buildSubject(); 16 this.builder.buildBody(); 17 this.builder.buildSendDate(); 18 this.builder.sendMessage(); 19 } 20 }
(8)客户端Client
1 public class Client { 2 public static void main(String[] args) { 3 // TODO Auto-generated method stub 4 Builder builder = new WelcomeBuilder(); 5 Director director = new Director(builder); 6 director.construct("toAddress@126.com", "fromAddress@126.com"); 7 8 } 9 }
3. 附加:建造模式分成两个很重要的部分:
(1). 一个部分是Builder接口,这里是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;
(2). 另外一个部分是Director,Director是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤地来执行。
不管如何变化,建造模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在建造模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式。
再直白点说,建造模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。