Java框架中常見的設計模式

前言

在Java框架中運用了很多的設計模式,常見的包括代理模式責任鏈模式觀察者模式普通工廠模式抽象工廠模式建造者模式等,那麼下面讓我們一起來了解一下。

1、什麼是設計模式

設計模式(Design pattern)代表了最佳的實踐,通常被有經驗的面向對象的軟件開發人員所採用。設計模式是軟件開發人員在軟件開發過程中面臨的一般問題的解決方案。這些解決方案是衆多軟件開發人員經過相當長的一段時間的試驗和錯誤總結出來的。

2、Java反射技術

Java反射技術應用廣泛,能夠配置類的全限定名方法參數完成對象的初始化等,增強了Java的可配置性,絕大數框架的基本原理也是基於這個技術。

我們簡單瞭解一下反射的應用:

2.1、通過反射構建對象

(1)通過無參構造方式構建對象,代碼如下:

public class ReflectServiceImpl {
    public void sayHello(String name) {
        System.err.println("Hello " + name);
    }

    public ReflectServiceImpl getInstance(){
        ReflectServiceImpl object =null;
        try {
            //通過反射生成對象
            object= (ReflectServiceImpl) Class.forName("com.ssm.learn.chapter2.reflect.ReflectServiceImpl").newInstance();
            
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return object;
    }
}

(2)通過有參構造方式構建對象,代碼如下:

public class ReflectServiceImpl2 {

    private String name;

    public ReflectServiceImpl2(String name) {
        this.name = name;
    }

    public void sayHello(String name) {
        System.err.println("Hello " + name);
    }

    public ReflectServiceImpl2 getInstance(){
        ReflectServiceImpl2 object =null;
        try {
             //通過反射生成對象
            object= (ReflectServiceImpl2) Class.forName("com.ssm.learn.chapter2.reflect.ReflectServiceImpl2")
                    .getConstructor(String.class)
                    .newInstance("張三");
            
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return object;
    }
}

  • 反射的優點:只要配置就可以生成對象,可以解除程序的耦合度。

  • 反射的缺點:運行較慢。

Spring IoC大部分情況下爲了靈活度、降低耦合度,而使用反射是值得的。

2.2、反射方法

代碼如下:

public Object reflectMethod() {
        Object returnObj = null;
        ReflectServiceImpl target = new ReflectServiceImpl();
        try {
            //Method method = ReflectServiceImpl.class.getMethod("sayHello", String.class);
            
            //當有具體對象target但不知道它是哪個類的可以這樣寫
            Method method = target.getClass().getMethod("sayHello", String.class);
            //調用方法,相當於target.sayHello("張三")
            returnObj = method.invoke(target, "張三");
            
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return returnObj;
    }

2.3、實例

代碼如下:

public static Object reflect() {
    ReflectServiceImpl object = null;
    try {
        object = (ReflectServiceImpl) Class.forName("com.ssm.learn.chapter2.reflect.ReflectServiceImpl")
                .newInstance();
        Method method = object.getClass().getMethod("sayHello", String.class);
        method.invoke(object,"張三");
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return object;
}

public static void main(String[] args) {
    ReflectServiceImpl.reflect();
}

運行程序,得到以下結果:

Hello 張三

3、動態代理模式

動態代理的意義在於生成一個佔位(又稱爲代理對象),來代理真實對象,從而控制真實對象的訪問。

舉個例子:客戶來軟件公司談需求不會直接跟軟件工程師談,而是去找商務談,客戶認爲商務代表了公司。如下圖所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dMUwLjJu-1582344884120)(C:\Users\AlanLiang\Desktop\新建文件夾 (2)]\代理模式示意圖.png)
代理的作用:在真實對象訪問之前或者之後加入對應的邏輯,或者根據其他規則控制是否使用真實對象。

代理分爲兩個步驟:

  1. 代理對象和真實對象之間建立代理關係

  2. 實現代理對象的代理邏輯方法

Java中最常用的動態代理技術有:

  • JDK動態代理。JDK自帶功能,必須使用接口,比較複雜

  • CGLIB。第三方提供技術,不必使用接口,比較簡單

3.1、JDK動態代理

首先定義HelloWorld接口,代碼如下:

public interface HelloWorld{
    public void sayHelloWorld();
}

然後提供實現類HelloWorldImpl來實現接口,代碼如下:

public class HelloWorldImpl implements HelloWorld{
    @Override
    public void sayHelloWorld(){
        System.out.println("Hello World");
    }
}

動態代理綁定和代理邏輯實現,代碼如下:

public class JdkProxyExample implements InvocationHandler {

    //真實對象
    private Object target = null;

    /**
     * 建立代理對象和真實對象的代理關係
     *
     * @param target 真實對象
     * @return 代理對象
     */
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * 代理方法邏輯
     * @param proxy 代理對象
     * @param method 當前調度方法
     * @param args 當前方法參數
     * @return 代理結果返回
     * @throws Throwable 異常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進入代理邏輯方法");
        System.out.println("在調用真實對象之前的服務");
        Object obj = method.invoke(target, args);//相當於調用sayHelloWord方法
        System.out.println("在調用真實對象之後的服務");
        return obj;
    }
}

測試JDK動態代理,代碼如下:

public class TestProxy {

    @Test
    public void testJdkProxy() {
        JdkProxyExample jdkProxy = new JdkProxyExample();
        //綁定關係,因爲掛在接口HelloWorld下,所以聲明代理對象HelloWorld proxy
        HelloWorld proxy = (HelloWorld) jdkProxy.bind(new HelloWorldImpl());
        //注意,此時proxy對象已經是一個代理對象,他會進入代理的邏輯方法invoke裏
        proxy.sayHelloWorld();
    }
}

運行結果如下:

進入代理邏輯方法
在調度真實對象之前的服務
Hello World
在調用真實對象之後的服務

3.2、CGLIB動態代理

代碼如下:

public class CglibProxyExample implements MethodInterceptor {

    /**
     * 生成CGLIB代理對象
     * @param clazz Class類
     * @return Class類的CGLIB對象
     */
    public Object getProxy(Class clazz) {
        //CGLIB enhancer增強類對象
        Enhancer enhancer = new Enhancer();
        //設置增強類型
        enhancer.setSuperclass(clazz);
        //定義代理邏輯對象爲當前對象,要求當前對象實現MethodInterceptor的方法
        enhancer.setCallback(this);
        //生成並返回代理對象
        return enhancer.create();
    }

    /**
     * 代理邏輯方法
     * @param proxy 代理對象
     * @param method 執行方法
     * @param args 方法參數
     * @param methodProxy 方法代理
     * @return 代理邏輯返回
     * @throws Throwable 異常
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用真實對象前");
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("調用真實對象後");
        return result;
    }
}

測試CGLIB動態代理,代碼如下:

public class TestProxy {

    @Test
    public void testCGLIBProxy(){
        CglibProxyExample cglibProxyExample = new CglibProxyExample();
        ReflectServiceImpl obj = (ReflectServiceImpl) cglibProxyExample.getProxy(ReflectServiceImpl.class);
        obj.sayHello("張三");
    }
}

測試結果如下:

調用真實對象前
Hello 張三
調用真實對象後

3.3、攔截器

由於動態代理一般比較難理解,程序開發者會設計一個攔截器接口供開發者使用,開發者只需要知道攔截器接口的方法、含義和作用即可,無須知道動態代理是如何實現的。

3.4、實例

用JDK動態代理來實現一個攔截器的邏輯,爲此先定義攔截器接口Interceptor,代碼如下:

public interface Interceptor {
    /**
     * 在真實對象前調用
     * @param proxy 代理對象
     * @param target 真實對象
     * @param method 調用方法
     * @param args 方法參數
     * @return 當返回true時則反射真實對象的方法,當返回false時則調用around方法
     * 當返回真實對象方法或者around方法執行後,調用after方法
     */
    public boolean before(Object proxy, Object target, Method method,Object[] args);

    public void around(Object proxy, Object target, Method method,Object[] args);

    public void after(Object proxy, Object target, Method method,Object[] args);
}

實現這個接口,代碼如下:

public class MyInterceptor implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法前邏輯");
        return false;//不反射被代理對象原有方法
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法後邏輯");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代了被代理對象的方法");
    }
}

在JDK動態代理中使用攔截器,代碼如下:

public class InterceptorJdkProxy implements InvocationHandler {
    //真實對象
    private Object target = null;
    //攔截器全限定名
    private String interceptorClass = null;

    public InterceptorJdkProxy(Object target, String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }


    /**
     * 綁定委託對象並返回一個[代理佔位]
     *
     * @param target 真實對象
     * @return 代理對象[佔位]
     */
    public static Object bind(Object target, String InterceptorClass) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InterceptorJdkProxy(target, InterceptorClass));
    }

    /**
     * 通過代理對象調用方法,首先進入這個方法
     *
     * @param proxy  代理對象
     * @param method 被調用方法
     * @param args   方法參數
     * @return 代理結果返回
     * @throws Throwable 異常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (interceptorClass == null) {
            //沒有設置攔截器則直接反射原有方法
            return method.invoke(target, args);
        }
        Object result = null;
        //通過反射生成攔截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();
        //調用前置方法
        if (interceptor.before(proxy, target, method, args)) {
            //返回true反射原有對象方法
            result = method.invoke(target, args);
        } else {
            //返回false執行around方法
            interceptor.around(proxy, target, method, args);
        }
        //調用後置方法
        interceptor.after(proxy, target, method, args);
        return result;
    }
}

代碼的執行步驟:

  1. 在bind方法中用JDK動態代理綁定了一個對象,然後返回代理對象。
  2. 如果沒有設置攔截器,則直接反射真實對象的方法,然後結束。否則,進行第三步。
  3. 通過反射生成攔截器,並準備使用它。
  4. 調用攔截器的before方法,如果返回true則反射原來的方法;否則運行攔截器的around方法。
  5. 調用攔截器的after方法
  6. 返回結果

測試攔截器,代碼如下:

public class TestInterceptor {

    @Test
    public void testInterceptor(){
       HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"com.ssm.learn.chapter2.interceptor.MyInterceptor");
       proxy.sayHelloWorld();
    }
}

測試結果如下:

反射方法前邏輯
反射方法後邏輯
取代了被代理對象的方法

4、責任鏈模式

舉個例子,一個程序員需要請假一週。如果把請假申請單看成是一個對象,那麼它需要經過項目經理、部門經理、人事經理等多個角色的審批,每個角色都有機會通過攔截這個申請單進行審批或者修改。這個時候就要考慮提供項目經理、部門經理和人事經理的處理邏輯了,所以需要提供3個攔截器,而傳遞的則是請假申請單,請假示例如圖所示:
在這裏插入圖片描述當一個對象在一條鏈上被多個攔截器攔截處理(攔截器也可以不攔截)時,我們把這樣的設計模式稱爲責任鏈模式,它用於一個對象在多個角色中傳遞的場景。

回到剛纔的例子,申請單來到項目經理那,經理可能把申請時間“一週”改爲“5天”,從而影響到後面的審批,因爲後面的審批會依據前面的結果進行。這時候考慮採用層層代理來實現,就是當申請單(target)來到項目經理處時,使用第一個動態代理proxy1,當他來到部門經理處時,部門經理會得到一個在項目經理的代理proxy1基礎上生成的第二個動態代理proxy2來處理部門經理的邏輯,以此類推。

攔截邏輯如下圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vzPfFFPh-1582344884122)(C:\Users\AlanLiang\Desktop\新建文件夾 (2)]\攔截邏輯.png)

4.1、實例

定義三個攔截器,代碼如下:
public class Interceptor1 implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器1】的before方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器1】的after方法");
    }
}
/************************************攔截器2*************************************/
public class Interceptor2 implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器2】的before方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器2】的after方法");
    }
}
/************************************攔截器3*************************************/
public class Interceptor3 implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器3】的before方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器3】的after方法");
    }
}

測試責任鏈模式,代碼如下:

@Test
public void testInterceptors() {
    HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(), "com.ssm.learn.chapter2.interceptor.Interceptor1");
    HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1, "com.ssm.learn.chapter2.interceptor.Interceptor2");
    HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2, "com.ssm.learn.chapter2.interceptor.Interceptor3");
    proxy3.sayHelloWorld();
}

運行結果如下:

【攔截器3】的before方法
【攔截器2】的before方法
【攔截器1】的before方法
Hello World
【攔截器1】的after方法
【攔截器2】的after方法
【攔截器3】的after方法

由此可見,責任鏈模式的優點是我們可以在傳遞鏈上加入新的攔截器,增加攔截邏輯,其缺點是會增加代理和反射,而代理和反射的性能不高。

5、觀察者(Observer)模式

觀察者模式又稱爲發佈訂閱模式,它定義了一種一對多的依賴關係,讓多個觀察者同時監視着被觀察者的狀態,當觀察者的狀態發生改變時,會通知所有觀察者,並讓其自動更新自己。

例子1:微博推送機制。當作者發佈一條新微博時,所有關注了該作者的粉絲都會收到推送。

例子2:一個商家有一些產品,他和一些電商合作,每當有新產品時,就會把這些產品推送到電商,現在只和淘寶、京東合作,於是有這樣的僞代碼:

if(產品庫有新產品){
	推送新產品到淘寶;
	推送新產品到京東;
}

如果公司又和國美、蘇寧、噹噹、唯品會簽訂合作協議,那麼就需要時改變這段僞代碼了:

if(產品庫有新產品){
	推送新產品到淘寶;
	推送新產品到京東;
	推送新產品到國美;
	推送新產品到蘇寧;
	推送新產品到噹噹;
	推送新產品到唯品會;
}

按照這種做法,如果還有其他電商合作,那麼還要繼續在if語句中增加邏輯。首先,如果電商越來越多,那麼if語句的邏輯就越來越複雜。而且,如果推送商品給淘寶發生異常,需要捕捉異常,避免影響之後的電商接口,導致其不能往下進行,這樣代碼耦合就會增多。其次,if語句堆砌太多代碼不利於維護和擴展。

**而觀察者模式更利用擴展,責任也更加清晰。**首先,把每一個電商接口看成一個觀察者,每一個觀察者都能觀察到產品列表(被監聽對象)。當公司發佈新產品時,就會發送到這個產品列表上,於是產品列表就發生了變化,這時就可以觸發各個電商接口(觀察者)發送新產品到對應的合作電商那裏,觀察者模式示例如圖所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0Ai7nPG8-1582344884123)(C:\Users\AlanLiang\Desktop\新建文件夾 (2)]\觀察者模式示例.jpg)
類似這樣,一個對象(電商接口)會去監聽另外一個對象(產品列表),當被監聽對象(產品列表)發生變化時,對象(電商接口)就會觸發一定的行爲,以適合變化的邏輯模式,我們稱爲觀察者模式。

這樣的好處在於,程序不再出現if語句,觀察者會根據被觀察者對象的變化而做出對應的行爲,無論是淘寶、京東或者其他電商團隊只要維護自己的邏輯,而無須耦合在一起。同時責任是明確的,產品團隊只需要維護產品列表,電商團隊可以增加觀察者去監聽產品的電商接口。

5.1、實例

被觀察者——產品列表,繼承Observable類,代碼如下:

public class ProductList extends Observable {
    //產品列表
    private List<String> productList = null;
    //類的唯一實例
    private static ProductList instance;

    //構造方法私有化
    private ProductList() {
    }

    /**
     * 取得唯一實例
     *
     * @return 產品列表唯一實例
     */
    public static ProductList getInstance() {
        if (instance == null) {
            instance = new ProductList();
            instance.productList = new ArrayList<>();
        }
        return instance;
    }

    /**
     * 增加觀察者(電商接口)
     * @param observer 觀察者
     */
    public void addProductListObserver(Observer observer) {
        this.addObserver(observer);
    }

    /**
     * 新增產品
     * @param newProduct 新產品
     */
    public void addProduct(String newProduct){
        productList.add(newProduct);
        System.out.println("產品列表增加了新的產品 "+newProduct);
        this.setChanged();//設置被觀察對象發生變化
        this.notifyObservers(newProduct);//通知觀察者,並傳遞新產品
    }
}

這裏的使用了構造方法私有化,避免通過new方式創建對象,而是通過getInstance()方法獲得產品列表單例,這裏使用了單例模式。

觀察者——以淘寶和京東爲例,實現他們的電商接口,作爲觀察者需要實現Observer接口的update方法,代碼如下:

public class TaoBaoObserver implements Observer {
    @Override
    public void update(Observable o, Object product) {
        String newProduct = (String) product;
        System.out.println("發送新產品【" + newProduct + "】同步到淘寶商城");
    }
}
public class JingDongObserver implements Observer {
    @Override
    public void update(Observable o, Object product) {
        String newProduct = (String) product;
        System.out.println("發送新產品【" + newProduct + "】同步到京東商城");
    }
}

測試觀察者模式,代碼如下:

ublic class TestObserver {
    @Test
    public void testObserver() {
        ProductList observable = ProductList.getInstance();
        TaoBaoObserver taoBaoObserver = new TaoBaoObserver();
        JingDongObserver jingDongObserver = new JingDongObserver();
        observable.addObserver(taoBaoObserver);
        observable.addObserver(jingDongObserver);
        observable.addProduct("新增產品1");
    }
}

運行結果如下:

產品列表增加了新的產品 新增產品1
發送新產品【新增產品1】同步到京東商城
發送新產品【新增產品1】同步到淘寶商城

6、工廠模式和抽象工廠模式

在大部分情況下,我們都是以new方式來創建對象。舉個例子,現實中車子的種類可能很多,有大巴車、轎車、救護車、越野車、卡車等,每個種類下面還有具體的型號,一個工廠生產如此多的車會難以管理,所以往往要進一步拆分爲各個分工廠:大巴車、轎車等分工廠。但是客戶不需要知道工廠如何拆分,他只會告訴客服需要什麼樣的車,客服就會根據客戶的要求找到對應的工廠去生產車。對客戶而言,車廠只是抽象概念,他只是大概知道有這樣的一個工廠能夠滿足他的需要。

6.1、普通工廠(Simple Factory)模式:

例如,有個IProduct的產品接口,它下面有5個實現類Product1Product2Product3Product4Product5。它們屬於一個大類,可以通過產品工廠去管理它們的生成,但是由於類型不同,所以初始化有所不同。爲了方便使用產品工廠(ProductFactory)類來創建這些產品的對象,用戶可以通過產品號來確定需要哪種產品,如圖所示:
在這裏插入圖片描述
ProductFactory的僞代碼如下:

public class ProductFactory{
    public static IProduct createProduct(String productNo){
        switch(productNo){
            case "1":return new Product1(XXX);
            case "2":return new Product2(XXX);
            case "3":return new Product3(XXX);
            case "4":return new Product4(XXX);
            case "5":return new Product5(XXX);
            default:
                throw new NotSupprotedException("未支持此編號產品生產。");
        }
    }
}

對於程序調用者而言,他只需要知道通過工廠的createProduct方法,指定產品編號——productNo可以得到對應的產品,而產品滿足接口IProduct的規範,所以初始化就簡單了許多。對於產品對象的創建,可以把一些特有產品規則寫入工廠類中。

6.2、抽象工廠(Abstract Factory)模式

抽象工廠模式可以向客戶端提供一個接口,使得客戶端在不必指定產品的具體情況下,創建多個產品族中的產品對象。有時候對象很複雜,有幾十種,又分爲幾個類別,如果使用簡單工廠會使得這個工廠的邏輯過於複雜。所以把工廠分爲好幾個,這樣便於工廠產品規則的維護。抽象工廠示意圖如圖所示:
在這裏插入圖片描述
爲了統一,需要制定一個接口規範(IProductFactory),所有的具體工廠和抽象工廠都要實現這一個接口,代碼如下:

public interface IProductFactory {
    public IProduct createProduct(String productNo);
}

現在再實現3個工廠類,代碼如下:

public class ProductFactory1 implements IProductFactory {
    @Override
    public IProduct createProduct(String productNo) {
        IProduct product = XXX;//工廠1生成產品對象規則,可以是一類產品的規則
        return product;
    }
}
public class ProductFactory2 implements IProductFactory {
    @Override
    public IProduct createProduct(String productNo) {
        IProduct product = XXX;//工廠2生成產品對象規則,可以是一類產品的規則
        return product;
    }
}
public class ProductFactory3 implements IProductFactory {
    @Override
    public IProduct createProduct(String productNo) {
        IProduct product = XXX;//工廠3生成產品對象規則,可以是一類產品的規則
        return product;
    }
}

使用一個公共的工廠,由它提供規則選擇工廠,我們做如下業務約定:產品編號以x開頭的用工廠ProductFactoryx創建對象。代碼如下:

public class ProductFactory implements IProductFactory {
    @Override
    public IProduct createProduct(String productNo) {
        char ch = productNo.charAt(0);
        IProductFactory factory = null;
        if (ch == '1') {
            factory = new ProductFactory1();
        } else if (ch == '2') {
            factory = new ProductFactory2();
        } else if (ch == '3') {
            factory = new ProductFactory3();
        }
        if (factory != null) {
            return factory.createProduct(productNo);
        }
        return null;
    }
}

對於設計者而言,ProductFactory就是一個抽象工廠,這樣創建對象對調用者而言就簡單多了。每一個工廠也只要維護其類型產品對象的生成,具體的工廠規則也不會特別複雜,難以維護。

7、建造者(Builder)模式

建造者模式可以將一個產品的內部表象(屬性)與產品的生成過程分割開來,從而使一個建造過程生成具有不同的內部表象的產品對象。

比如一些旅遊套票可以分爲:普通成年人、退休老年人、半票有座小孩、免費無座小孩、軍人及其家屬等,他們有不同的規定和優惠。如果通過new或者工廠模式來創建對象會造成不便,因爲參數過多,對象也複雜。

Builder模式是一種分步構建對象的模式。用一個配置類對各步進行統籌,然後將所有信息交由構造器來完成構造對象。

7.1、實例

創建一個配置類的對象TickerHelper,它能夠幫助我們一步步構建對象。代碼如下所示:

public class TicketHelper {
    public void buildAdult(String info) {
        System.out.println("構建成年人票邏輯:" + info);
    }

    public void buildChildrenForSeat(String info) {
        System.out.println("構建有座兒童票邏輯:" + info);
    }

    public void buildChildrenForNoSeat(String info) {
        System.out.println("構建無座兒童票邏輯:" + info);
    }

    public void buildElderly(String info) {
        System.out.println("構建老年人票邏輯:" + info);
    }

    public void buildSoldier(String info) {
        System.out.println("構建軍人及其家屬票邏輯:" + info);
    }
}

然後,需要一個構造類——TicketBuilder,代碼如下:

public class TicketBuilder {
    public static Object builder(TicketHelper helper){
        System.out.println("通過TicketHelper構建套票信息");
        return null;
    }
}

通過這兩個類就可以構建出套票,代碼如下:

public class TestBuilder {
    @Test
    public void testBuilder() {
        TicketHelper helper = new TicketHelper();
        helper.buildAdult("成人票");
        helper.buildChildrenForSeat("有座兒童");
        helper.buildChildrenForNoSeat("無座兒童");
        helper.buildElderly("老年人票");
        helper.buildSoldier("軍人票");
        Object ticket = TicketBuilder.builder(helper);
    }
}

本文參考楊開振的《JavaEE互聯網輕量級框架整合開發》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章