設計模式-工廠模式-場景以及優缺點-目的就是應對變化 (國江面試回答的)


 總結:

我自己寫了個例子:有興趣的可以下載看看,參考:工廠模式

工廠方法模式:
一個抽象產品類,可以派生出多個具體產品類。   
一個抽象工廠類,可以派生出多個具體工廠類。   
每個具體工廠類只能創建一個具體產品類的實例。

抽象工廠模式:
多個抽象產品類,每個抽象產品類可以派生出多個具體產品類。   
一個抽象工廠類,可以派生出多個具體工廠類。   
每個具體工廠類可以創建多個具體產品類的實例。   
    
區別:
工廠方法模式只有一個抽象產品類,而抽象工廠模式有多個。   
工廠方法模式的具體工廠類只能創建一個具體產品類的實例,而抽象工廠模式可以創建多個。 
 

一、工廠模式的好處:

(1) 工廠模式是為了解耦可以將對象的創建和使用分離,如果不分離,不但違反了設計模式的開閉原則,需要需要使用另一個子類的話,需要修改源代碼 ,把對象的創建和使用的過程分開。就是Class A 想調用 Class B ,那么A只是調用B的方法,而至於B的實例化,就交給工廠類。

(2)工廠模式可以降低代碼重復。如果創建對象B的過程都很復雜,需要一定的代碼量,而且很多地方都要用到,那么就會有很多的重復代碼。我們可以這些創建對象B的代碼放到工廠里統一管理。既減少了重復代碼,也方便以后對B的創建過程的修改維護。(當然,我個人覺得也可以把這些創建過程的代碼放到類的構造函數里,同樣可以降低重復率,而且構造函數本身的作用也是初始化對象。不過,這樣也會導致構造函數過於復雜,做的事太多,不符合java 的設計原則。)

由於創建過程都由工廠統一管理,所以發生業務邏輯變化,不需要找到所有需要創建B的地方去逐個修正,只需要在工廠里修改即可,降低維護成本。同理,想把所有調用B的地方改成B的子類B1,只需要在對應生產B的工廠中或者工廠的方法中修改其生產的對象為B1即可,而不需要找到所有的new B()改為new B1()。

(3)因為工廠管理了對象的創建邏輯,使用者並不需要知道具體的創建過程,只管使用即可,減少了使用者因為創建邏輯導致的錯誤 

(4)可以通過參數設置,返回不同的構造函數,不需要修改使用類的地方。如果一個類有多個構造方法(構造的重寫),我們也可以將它抽出來,放到工廠中,一個構造方法對應一個工廠方法並命名一個友好的名字,這樣我們就不再只是根據參數的不同來判斷,而是可以根據工廠的方法名來直觀判斷將要創建的對象的特點。這對於使用者來說,體驗比較好。

舉個例子:

一個數據庫工廠:可以返回一個數據庫實例,可以是mysql,oracle等。

這個工廠就可以把數據庫連接需要的用戶名,地址,密碼等封裝好,直接返回對應的數據庫對象就好。不需要調用者自己初始化,減少了寫錯密碼等等這些錯誤。調用者只負責使用,不需要管怎么去創建、初始化對象。 

      在Java語言中,我們通常有以下幾種創建對象的方式:

       (1) 使用new關鍵字直接創建對象;

       (2) 通過反射機制創建對象;

       (3) 通過clone()方法創建對象;

       (4) 通過工廠類創建對象。

      毫無疑問,在客戶端代碼中直接使用new關鍵字是最簡單的一種創建對象的方式,但是它的靈活性較差,下面通過一個簡單的示例來加以說明: 

復制代碼
class LoginAction {  
    private UserDAO udao;  
      
    public LoginAction() {  
        udao = new JDBCUserDAO(); //創建對象  
    }  
      
    public void execute() {  
        //其他代碼  
        udao.findUserById(); //使用對象  
        //其他代碼  
    }  
}  
復制代碼

 

      在LoginAction類中定義了一個UserDAO類型的對象udao,在LoginAction的構造函數中創建了JDBCUserDAO類型的udao對象,並在execute()方法中調用了udao對象的findUserById()方法,這段代碼看上去並沒有什么問題。下面我們來分析一下LoginAction和UserDAO之間的關系,LoginAction類負責創建了一個UserDAO子類的對象並使用UserDAO的方法來完成相應的業務處理,也就是說LoginAction即負責udao的創建又負責udao的使用,創建對象和使用對象的職責耦合在一起,這樣的設計會導致一個很嚴重的問題:如果在LoginAction中希望能夠使用UserDAO的另一個子類如HibernateUserDAO類型的對象,必須修改LoginAction類的源代碼,違反了“開閉原則”。如何解決該問題?(對設計模式六大原則不了解參考:23種設計模式介紹(一)---- 創建型模式)

      最常用的一種解決方法是將udao對象的創建職責從LoginAction類中移除,在LoginAction類之外創建對象,那么誰來負責創建UserDAO對象呢?答案是:工廠類。通過引入工廠類,客戶類(如LoginAction)不涉及對象的創建,對象的創建者也不會涉及對象的使用。引入工廠類UserDAOFactory之后的結構如圖1所示:

圖1 引入工廠類之后的結構圖

       工廠類的引入將降低因為產品或工廠類改變所造成的維護工作量。如果UserDAO的某個子類的構造函數發生改變或者要需要添加或移除不同的子類,只要維護UserDAOFactory的代碼,而不會影響到LoginAction;如果UserDAO的接口發生改變,例如添加、移除方法或改變方法名,只需要修改LoginAction,不會給UserDAOFactory帶來任何影響。

       在所有的工廠模式中,我們都強調一點:兩個類A和B之間的關系應該僅僅是A創建B或是A使用B,而不能兩種關系都有。將對象的創建和使用分離(美團面試題,工廠模式的優點),也使得系統更加符合“單一職責原則”,有利於對功能的復用和系統的維護。

       此外,將對象的創建和使用分離還有一個好處:防止用來實例化一個類的數據和代碼在多個類中到處都是,可以將有關創建的知識搬移到一個工廠類中,這在Joshua Kerievsky的《重構與模式》一書中有專門的一節來進行介紹。因為有時候我們創建一個對象不只是簡單調用其構造函數,還需要設置一些參數,可能還需要配置環境,如果將這些代碼散落在每一個創建對象的客戶類中,勢必會出現代碼重復、創建蔓延的問題,而這些客戶類其實無須承擔對象的創建工作,它們只需使用已創建好的對象就可以了。此時,可以引入工廠類來封裝對象的創建邏輯和客戶代碼的實例化/配置選項。

      使用工廠類還有一個“不是特別明顯的”優點,一個類可能擁有多個構造函數,而在Java、C#等語言中構造函數名字都與類名相同,客戶端只能通過傳入不同的參數來調用不同的構造函數創建對象,從構造函數和參數列表中也許大家根本不了解不同構造函數所構造的產品的差異。但如果將對象的創建過程封裝在工廠類中,我們可以提供一系列名字完全不同的工廠方法,每一個工廠方法對應一個構造函數,客戶端可以以一種更加可讀、易懂的方式來創建對象,而且,從一組工廠方法中選擇一個意義明確的工廠方法,比從一組名稱相同參數不同的構造函數中選擇一個構造函數要方便很多。如圖2所示:

       在圖2中,矩形工廠類RectangleFactory提供了兩個工廠方法createRectangle()和createSquare(),一個用於創建長方形,一個用於創建正方形,這兩個方法比直接通過構造函數來創建長方形或正方形對象意義更加明確,也在一定程度上降低了客戶端調用時出錯的概率。

      那么,有人可能會問,是否需要為設計中的每一個類都配備一個工廠類?答案是:具體情況具體分析。如果產品類很簡單,而且不存在太多變數,其構造過程也很簡單,此時無須為其提供工廠類,直接在使用之前實例化即可,例如Java語言中的String類,我們就無須為它專門提供一個StringFactory,這樣做反而有點像殺雞用牛刀,大材小用,而且會導致工廠泛濫,增加系統的復雜度

 

二、工廠模式適用的一些場景(不僅限於以下場景):

1. 對象的創建過程/實例化准備工作很復雜,需要初始化很多參數、查詢數據庫等。

2.類本身有好多子類,這些類的創建過程在業務中容易發生改變,或者對類的調用容易發生改變。 

以下是關於工廠模式的其他的描述:

1.模式描述

提供一個用於創建對象的接口(工廠接口),讓其實現類(工廠實現類)決定實例化哪一個類(產品類),並且由該實現類創建對應類的實例。   

2.模式作用

可以一定程度上解耦,消費者和產品實現類隔離開,只依賴產品接口(抽象產品),產品實現類如何改動與消費者完全無關。

可以一定程度增加擴展性,若增加一個產品實現,只需要實現產品接口,修改工廠創建產品的方法,消費者可以無感知(若消費者不關心具體產品是什么的情況)。
可以一定程度增加代碼的封裝性、可讀性。清楚的代碼結構,對於消費者來說很少的代碼量就可以完成很多工作。
等等。//TODO
另外,抽象工廠才是實際意義的工廠模式,工廠方法只是抽象工廠的一個比較常見的情況。

3.適用場景

消費者不關心它所要創建對象的類(產品類)的時候。

消費者知道它所要創建對象的類(產品類),但不關心如何創建的時候。

等等。//TODO

例如:hibernate里通過sessionFactory創建session、通過代理方式生成ws客戶端時,通過工廠構建報文中格式化數據的對象。

4.模式要素

提供一個產品類的接口。產品類均要實現這個接口(也可以是abstract類,即抽象產品)。
提供一個工廠類的接口。工廠類均要實現這個接口(即抽象工廠)。
由工廠實現類創建產品類的實例。工廠實現類應有一個方法,用來實例化產品類。

5.類圖

 

6.模式實例代碼

工廠:

package com.demoFound.factoryMethod.factory;

import com.demoFound.factoryMethod.message.IMyMessage;

/**
 * 工廠方法模式_工廠接口
 * 
 * @author popkidorc
 * 
 */
public interface IMyMessageFactory {

    public IMyMessage createMessage(String messageType);
}
package com.demoFound.factoryMethod.factory;

import java.util.HashMap;
import java.util.Map;

import com.demoFound.factoryMethod.message.IMyMessage;
import com.demoFound.factoryMethod.message.MyMessageEmail;
import com.demoFound.factoryMethod.message.MyMessageOaTodo;
import com.demoFound.factoryMethod.message.MyMessageSms;

/**
 * 工廠方法模式_工廠實現
 * 
 * @author popkidorc
 * 
 */
public class MyMessageFactory implements IMyMessageFactory {

    @Override
    public IMyMessage createMessage(String messageType) {
        // 這里的方式是:消費者知道自己想要什么產品;若生產何種產品完全由工廠決定,則這里不應該傳入控制生產的參數。
        IMyMessage myMessage;
        Map<String, Object> messageParam = new HashMap<String, Object>();
        // 根據某些條件去選擇究竟創建哪一個具體的實現對象,條件可以傳入的,也可以從其它途徑獲取。
        // sms
        if ("SMS".equals(messageType)) {
            myMessage = new MyMessageSms();
            messageParam.put("PHONENUM", "123456789");
        } else
        // OA待辦
        if ("OA".equals(messageType)) {
            myMessage = new MyMessageOaTodo();
            messageParam.put("OAUSERNAME", "testUser");
        } else
        // email
        if ("EMAIL".equals(messageType)) {
            myMessage = new MyMessageEmail();
            messageParam.put("EMAIL", "test@test.com");
        } else
        // 默認生產email這個產品
        {
            myMessage = new MyMessageEmail();
            messageParam.put("EMAIL", "test@test.com");
        }
        myMessage.setMessageParam(messageParam);
        return myMessage;
    }
}

產品:

package com.demoFound.factoryMethod.message;

import java.util.Map;

/**
 * 工廠方法模式_產品接口
 * 
 * @author popkidorc
 * 
 */
public interface IMyMessage {

    public Map<String, Object> getMessageParam();

    public void setMessageParam(Map<String, Object> messageParam);

    public void sendMesage() throws Exception;// 發送通知/消息

}
package com.demoFound.factoryMethod.message;

import java.util.Map;

/**
 * 工廠方法模式_虛擬產品類
 * 
 * @author popkidorc
 * 
 */
public abstract class MyAbstractMessage implements IMyMessage {

    private Map<String, Object> messageParam;// 這里可以理解為生產產品所需要的原材料庫。最好是個自定義的對象,這里為了不引起誤解使用Map。

    @Override
    public Map<String, Object> getMessageParam() {
        return messageParam;
    }

    @Override
    public void setMessageParam(Map<String, Object> messageParam) {
        this.messageParam = messageParam;
    }
}
package com.demoFound.factoryMethod.message;

/**
 * 工廠方法模式_email產品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageEmail extends MyAbstractMessage {

    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam() || null == getMessageParam().get("EMAIL")
                || "".equals(getMessageParam().get("EMAIL"))) {
            throw new Exception("發送短信,需要傳入EMAIL參數");// 為了簡單起見異常也不自定義了
        }// 另外郵件內容,以及其他各種協議參數等等都要處理

        System.out.println("我是郵件,發送通知給" + getMessageParam().get("EMAIL"));
    }

}
package com.demoFound.factoryMethod.message;

/**
 * 工廠方法模式_oa待辦產品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageOaTodo extends MyAbstractMessage {

    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam()
                || null == getMessageParam().get("OAUSERNAME")
                || "".equals(getMessageParam().get("OAUSERNAME"))) {
            throw new Exception("發送OA待辦,需要傳入OAUSERNAME參數");// 為了簡單起見異常也不自定義了
        }// 這里的參數需求就比較多了不一一處理了

        System.out
                .println("我是OA待辦,發送通知給" + getMessageParam().get("OAUSERNAME"));
    }

}
package com.demoFound.factoryMethod.message;

/**
 * 工廠方法模式_sms產品
 * 
 * @author popkidorc
 * 
 */
public class MyMessageSms extends MyAbstractMessage {

    @Override
    public void sendMesage() throws Exception {
        // TODO Auto-generated method stub
        if (null == getMessageParam()
                || null == getMessageParam().get("PHONENUM")
                || "".equals(getMessageParam().get("PHONENUM"))) {
            throw new Exception("發送短信,需要傳入PHONENUM參數");// 為了簡單起見異常也不自定義了
        }// 另外短信信息,以及其他各種協議參數等等都要處理

        System.out.println("我是短信,發送通知給" + getMessageParam().get("PHONENUM"));
    }

}

消費者:

package com.demoFound.factoryMethod;

import com.demoFound.factoryMethod.factory.IMyMessageFactory;
import com.demoFound.factoryMethod.factory.MyMessageFactory;
import com.demoFound.factoryMethod.message.IMyMessage;

/**
 * 工廠方法模式_消費者類
 * 
 * @author popkidorc
 * 
 */
public class MyFactoryMethodMain {

    public static void main(String[] args) {
        IMyMessageFactory myMessageFactory = new MyMessageFactory();
        IMyMessage myMessage;
        // 對於這個消費者來說,不用知道如何生產message這個產品,耦合度降低
        try {
            // 先來一個短信通知
            myMessage = myMessageFactory.createMessage("SMS");
            myMessage.sendMesage();

            // 來一個oa待辦
            myMessage = myMessageFactory.createMessage("OA");
            myMessage.sendMesage();

            // 來一個郵件通知
            myMessage = myMessageFactory.createMessage("EMAIL");
            myMessage.sendMesage();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

參考:關於工廠模式,一般什么情況下使用?

參考:Java技術_每天掌握一種設計模式(003)_使用場景及簡單實例(創建型:工廠方法)

參考:關於工廠模式的作用。為什么要用工廠模式?

參考:創建對象與使用對象——談談工廠的作用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM