今天我分享以下设计模式中的比较经典的生成器模式(Builder),在介绍该模式之前,我还是把设计模式分类图贴出来,方便查看整体结构和分布。
在工厂方法模式和抽象工厂设计模式的分享中,我们曾经提及,有时候对于调用者来说由于依赖的对象可能是一个复杂的接口实现,所以创建这个对象对于调用者来说有些难度,并且调用者即使创建了对象,那样也使创建对象的过程出现在了调用者的方法中,这样使对象间的耦合性大大增加,不利于系统的维护和以后产品的扩展。工厂方法模式和抽象工厂模式都将对象的创建过程封装在了工厂方法中,这样调用者只要知道产品的工厂即可以利用其创建相关的产品对象,这样创建的对象通过第三者(工厂)实现了解耦,以后如果产品对象需要扩展的话就不会去修改调用者,并且工厂和产品之间都是通过抽象接口关联的,所以其扩展性可利用继承或者实现来达到,这样使产品变化对整体系统的影响降到了最低点。
工厂方法和抽象工厂聚焦于产品的创建,今天所分享的生成器和前两者的用意相似,但是生成器更侧重于复杂产品的创建过程,并且该产品的创建细节需要满足可变性,比如对于一个汽车的创建过程,可能会根据汽车轮子的个数来创建不同的汽车,当然可能根据不同的汽车最大行程数创建油箱大小等等。也就是说这个生成器可以生成一个对象,这个对象需要根据外部的状态来改变创建的对象的细节,并且由具体的指导者指导其一步步组装或者创建。
意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
分类:对象型创建模式
适用情况:在以下情况使用Build模式:
-
当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
-
当构造过程必须允许被构造的对象有不同的表示时。
-
Builder模式要解决的也正是这样的问题:
当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要复杂对象的创建过程和这个对象的表示(展示)分离开来,这样做的好处就是通过一步步的进行复杂对象的构建,由于在每一步的构造过程中可以引入参数,使得经过相同的步骤创建最后得到的对象的展示不一样。
结构UML图:
从类图中可以看到,生成器模式是由四种角色组成的,其中:
-
Builder 为创建Product对象的各个部件制定抽象接口。
-
ConcreteBuilder 实现Builder 的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,提供一个检索产品的接口。
-
Director 构造一个使用Builder接口的对象。
-
Product 表示被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程。包含定义组成组件的类,包括将这些组件装配成最终产品的接口。
模式标准代码:
//产品类
class Product {
private String part1;
private String part2;
public void setPart1(String part1){
this.part1=part1;
}
public void setPart2(String part2){
this.part2=part2;
}
public void showProduct(){
System.out.println("第一部分:"+part1
+"\n"+"第二部分:"+part2);
}
}
//抽象生成器
abstract class Builder {
public abstract void buildPart1();
public abstract void buildPart2();
public abstract Product getProduct();
}
//具体生成器
class ConcreteBuilder extends Builder {
private Product product = new Product();
public void buildPart1() {
product.setPart1("核心产品");
}
public void buildPart2() {
product.setPart2("产品包装");
}
public Product getProduct() {
returnproduct;
}
}
//导演类
class Director {
private Builder builder;
public Director(Builder builder){
this.builder=builder;
}
public void construct(){
/*注意调用buildPart1和buildPart2正是
UML图中for all objects in structure
{builder->buildPart()}代表的意思
即用Builder去构造产品的所有构造部分,
其中objects在这里表示part1:String
part2:String对象,当然也可以是复杂对象
这里只是为了写标准代码。*/
builder.buildPart1();
builder.buildPart2();
}
}
//客户端
public class Client {
public static void main(String[] args) {
Builder builder=new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product1 = builder.getProduct();
product1.showProduct();
}
}
从上面的标准代码来看,Builder 模式还是甚是简单的,不过,Builder模式的精髓可不是一两下就能看明白的,特别是当熟悉了工厂模式后,再学习这个模式的时候,有时候就很糊涂,这里我再结合相关图示解释一下Builder设计模式:
Builder 设计模式中有四个角色,如果将Builder和ConcreteBuilder归为一个角色后就只剩下仅仅三个角色了,即:Product、Builder和Director。那么这三个角色是如何协作完成产品的创建的呢?下面是该模式的序列图,让我们借助对象工作序列图来分析:
从序列图中可看到客户端(Cilent)首先创建能够返回产品的生成器(ConcreteBuilder),事实上,也只有生成器才能返回Product(见图中返回虚线),但是虽然生成器中有一堆构造Product的方法(如:buildPart1()、buildPart2()),可是它自己却不能且不知道怎么构造产品。这时候客户端就想到派一个技术专家角色(Director)来指挥生成器生产产品,于是技术专家根据需要向生成器发出buildPart1()和buildPart2()命令。生成器在收到这两个命令后开始调用Product方法来来一步步构建产品。产品构建完成后,生成器不会主动返回产品,因为它不知道返回给谁,技术专家也不需要这个产品,这个产品是需要交给最终需要产品的客户端,除非客户端发出获取产品的命令,否则这个产品即使是创建好了也有可能烂尾。这样解释我想应该差不多理解了吧。
如果思路还不清晰,我就打个比方:项目经理有个产品需要开发,他知道开发产品需要程序员,于是他找到一个程序员。但是程序员只会按照产品设计图来开发,所以项目经理就找来一个软件设计师来设计产品并把这个程序员交给他负责。软件设计师设计好产品开发图后,告诉程序员,先开发什么,再开发什么,以及怎么组装产品。程序员根据软件设计师的要求开发产品。项目经理必定知道开发是否完工,当开发完工的时候,项目经理说,某某程序员给我打包产品,于是产品开发就完成了。
应用场景例子():标准SQL转换成Mybaits的XML文档:
标准Mybaits XML文档如图所示:
SELECT ID, NAME, AGE FROM PERSON
AND ID=#{id}
AND NAME LIKE '%'||#{name}||'%'
AND AGE=#{age}
我的目标就是利用生成器标准的PERSON表的SQL文件自动生成Mybaits文件,PERSON表的SQL:
-- Create table
create table PERSON
(
id VARCHAR2(32),
name VARCHAR2(32),
age NUMBER
)
tablespace USERS
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
minextents 1
maxextents unlimited
);
-- Add comments to the table
comment on table PERSON
is '????';
-- Add comments to the columns
comment on column PERSON.id
is 'ID';
comment on column PERSON.name
is '????';
comment on column PERSON.age
is '????';
UML图如下:
代码结构:
package com.code2note;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
//XML头
class XMLHead{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
//XML标签前缀如:
class XMLPrefix{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
//XML标签尾缀如:
class XMLSuffix{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
//XML标签间的内容:sql语句
class XMLContent{
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public void setContent(XMLContent content) {
this.content = content.getContent();
}
}
//SQL变量内容
class SQLContent implements Cloneable{
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
protected SQLContent clone()
throws CloneNotSupportedException {
return (SQLContent) super.clone();
}
}
//读取标准表创建的sql文件
//比较复杂,但是都是很简单的语句
class SQLFileReader{
//读取sql标准文件
public List
readSqlFile(String path){
BufferedReader reader=null;
try{
File file=new File(path);
FileReader fReader=new FileReader(file);
reader=new BufferedReader(fReader);
boolean varsStart=false;
boolean varsEnd=false;
List
varsRow=new ArrayList<>();
String readLine=reader.readLine();
while(readLine!=null){
if(readLine.startsWith("create table")){
varsRow.add(readLine);//表名
}
if(readLine.contains(")")
&&!readLine.contains(",")){
varsEnd=true;
break;
}
if(varsStart&&!varsEnd){
varsRow.add(readLine);
}
if(readLine.contains("(")){
varsStart=true;
}
readLine=reader.readLine();
}
return varsRow;
}catch(IOException e){
e.printStackTrace();
}finally{
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
//解析属性、类型和表名
public Map
getSqlPropertys(String path){
HashMap
propertyMap
=new HashMap
(); List
varsRow =this.readSqlFile(path); SQLContent content =new SQLContent(); for(int i=0;i
=0;j--){ if(temp[j]!=null&&temp[j]!="" &&!temp[j].contains("create") &&!temp[j].contains("table") ){ sqlContent.setName("-1"); sqlContent.setType(temp[j]); break; } } propertyMap.put(sqlContent.getName(), sqlContent); continue; } String []temp=str.split(" "); boolean first=false; for(int j=0;j
"); mybaits.setXmlPrefix(xmlPrefix); } public void buildXMLSuffix(){ xmlSuffix.setValue(""); mybaits.setXmlSuffix(xmlSuffix); } public void buildContent(String path){ Map
map =SQLFileReader.getSqlPropertys(path); String selContent="\tSELECT "; String sqlWhereStart="
\n\t"; String sqlWhere=""; String sqlWhereEnd="\n\t
"; String tableName=""; String props=""; Set
set=map.keySet(); Iterator
its=set.iterator(); while(its.hasNext()){ String key=(String) its.next(); SQLContent sqlContent=map.get(key); if("-1".equals(key)){ //获取表名所在行 tableName=sqlContent.getType(); }else{ props+=key+","; String type=sqlContent.getType(); String jdbcType=""; if(type.contains("VARCHAR")){ jdbcType="VARCHAR"; }else if(type.contains("NUMBER")){ jdbcType="INTEGER"; }else if(type.contains("DATE")){ jdbcType="DATE"; }else if(type.contains("TIMESTAMP")){ jdbcType="TIMESTAMP"; }else{ jdbcType="VARCHAR"; } if("VARCHAR".equals(jdbcType)){ sqlWhere+="\n\t\t
AND "+key.toUpperCase()+" LIKE'%'||#{" +key+",jdbcType="+jdbcType+"}||'%'
"; }else{ sqlWhere+="\n\t\t
AND "+key.toUpperCase()+" =#{" +key+",jdbcType="+jdbcType+"}
"; } } } selContent+=props.subSequence(0, props.lastIndexOf(",")) +" FROM "+tableName+"\n\t" +sqlWhereStart+sqlWhere+sqlWhereEnd; xmlContent.setContent(selContent); mybaits.setXmlContent(xmlContent); } //返回组装的文件 public Mybaits getMyBaits(){ return mybaits; } } //其它标签内容限于篇幅就不贴上来了 class MybaitsXMLInsertBuilder{} class MybaitsXMLUpdateBuilder{} class MybaitsXMLDeleteBuilder{} //导演类调用builder生成xml文件 class MybaitsDirector{ private MybaitsXMLSelectBuilder selectBuilder; public MybaitsDirector(MybaitsXMLSelectBuilder selectBuilder){ this.selectBuilder=selectBuilder; } public void construct(){ //构造mybaits文件 //这里只是构造select selectBuilder.buildXMLHead(); selectBuilder.buildXMLPrefix(); selectBuilder.buildXMLSuffix(); selectBuilder.buildContent("D:/mybaits.sql"); } } //客户端 public class MyBaitsClient { public static void main(String[] args) throws IOException, CloneNotSupportedException { MybaitsXMLSelectBuilder selectBuilder=new MybaitsXMLSelectBuilder(); MybaitsDirector director=new MybaitsDirector(selectBuilder); director.construct(); selectBuilder.getMyBaits().showMyBaits(); } }
---------------------------
端午节,祝大家:端午安康。我也要休息一下了。顺便说一下,为什么这篇分享的开头是做面包的呢?其实还是肚子有些饿了,想到用生成器生成面包呢,但是最近由于工作需要才想到用生成Mybaits文件为例子进行演示,事实上生成Mybaits文件的程序部分写的并不是很严谨和高效,但是我想还是能够很好的体现生成器模式吧,希望喜欢这个程序的朋友可以查看本期的其他两个文件,将代码Copy出来研究并重构一下,我想一定是个不错的程序。