今天我分享以下設計模式中的比較經典的生成器模式(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(); } }
生成的slect 截圖:
---------------------------
端午節,祝大家:端午安康。我也要休息一下了。順便說一下,為什麼這篇分享的開頭是做面包的呢?其實還是肚子有些餓了,想到用生成器生成面包呢,但是最近由于工作需要才想到用生成Mybaits檔案為例子進行示範,事實上生成Mybaits檔案的程式部分寫的并不是很嚴謹和高效,但是我想還是能夠很好的展現生成器模式吧,希望喜歡這個程式的朋友可以檢視本期的其他兩個檔案,将代碼Copy出來研究并重構一下,我想一定是個不錯的程式。