面向?qū)ο缶幊淘O(shè)計模式------工廠模式(簡單工廠、工廠方法、抽象工廠)

1、簡單工廠模式

??簡單工廠模式又叫靜態(tài)工廠方法模式(Static Factory Method Pattern),是通過專門定義一個類來負(fù)責(zé)創(chuàng)建其他類的實例,被創(chuàng)建的實例通常都具有共同的父類。
??一個簡單的實例:要求實現(xiàn)一個計算機控制臺程序,要求輸入數(shù)的運算結(jié)果。最原始的解決方法如下:

/**
 * @Description:這里使用的是最基本的實現(xiàn),并沒有體現(xiàn)出面向?qū)ο蟮木幊趟枷?,代碼的擴展性差,甚至連除數(shù)可能為0的情況也沒有考慮
 */
public static void main(String[] args) {
    scanner = new Scanner(System.in);
    
    System.out.print("請輸入第一個數(shù)字:");
    int firstNum = scanner.nextInt();
    
    System.out.print("請輸入第二個數(shù)字:");
    int secondNum = scanner.nextInt();
    
    System.out.print("請輸入運算符:");
    String operation = scanner.next();
    
    if(operation.equals("+")) {
        System.out.println("result:" + (firstNum + secondNum));
    } else if(operation.equals("-")) {
        System.out.println("result:" + (firstNum - secondNum));
    } else if(operation.equals("*")) {
        System.out.println("result:" + (firstNum * secondNum));
    } else if(operation.equals("/")){
        System.out.println("result:" + (firstNum / secondNum));
    }
}

??上面的寫法實現(xiàn)雖然簡單,但是卻沒有面向?qū)ο蟮奶匦?,代碼拓展性差,甚至沒有考慮除數(shù)可能為0的特殊情況。
??在面向?qū)ο缶幊陶Z言中,一切都是對象,所以上面運算符號也應(yīng)當(dāng)作對象來處理。因此我們首先建立一個運算接口,所有其他的運算都封裝成類,并實現(xiàn)該運算接口。

/**
 * @Description: 定義一個運算接口,將所有的運算符號都封裝成類,并實現(xiàn)本接口
 * @author: zxt
 * @time: 2018年7月6日 上午10:24:13
 */
public interface Operation {
    public double getResult(double firstNum, double secondNum);
}

public class AddOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum + secondNum;
    }
}

public class SubOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum - secondNum;
    }
}

public class MulOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        return firstNum * secondNum;
    }
}

public class DivOperation implements Operation {
    @Override
    public double getResult(double firstNum, double secondNum) {
        if(secondNum == 0) {
            try {
                throw new Exception("除數(shù)不能為0!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        return firstNum / secondNum;
    }
}

??現(xiàn)在的問題的是,如何根據(jù)不同的情況創(chuàng)建不同的對象,這里就可以使用簡單工廠模式來實現(xiàn)了,客戶端只需要提供運算符,工廠類會判斷并生成相應(yīng)的運算類:

/**
 * @Description: 簡單工廠模式:通過一個工廠類,根據(jù)情況創(chuàng)建不同的對象
 * @author: zxt
 * @time: 2018年7月6日 上午10:50:15
 */
public class OperationFactory {
    /**
     * @Description:根據(jù)運算符得到具體的運算類
     * @param operationStr
     */
    public static Operation getOperation(String operationStr) {
        Operation result = null;
        
        switch(operationStr) {
        case "+":
            result = new AddOperation();
            break;
        case "-":
            result = new SubOperation();
            break;
        case "*":
            result = new MulOperation();
            break;
        case "/":
            result = new DivOperation();
            break;
        }
        
        return result;
    }
}

// 客戶端調(diào)用
Operation oper = OperationFactory.getOperation(operation);
double result = oper.getResult(firstNum, secondNum);
System.out.println(result);

??簡單工廠將對象的創(chuàng)建過程進(jìn)行了封裝,用戶不需要知道具體的創(chuàng)建過程,只需要調(diào)用工廠類獲取對象即可。
??這種簡單工廠的寫法是通過switch-case來判斷對象創(chuàng)建過程的。在實際使用過程中,違背了開放-關(guān)閉原則(例如,當(dāng)需要擴展一個新的運算符之后,簡單工廠創(chuàng)建的對象也必須多一種,這就需要修改原來的代碼了,違背了對修改關(guān)閉的原則),當(dāng)然有些情況下可以通過反射調(diào)用來彌補這種不足。

2、工廠方法模式

??簡單工廠模式的最大優(yōu)點在于工廠類中包含了必要的邏輯判斷,根據(jù)客戶端的選擇條件動態(tài)實例化相關(guān)的類,對于客戶端來說,去除了與具體產(chǎn)品的依賴。但是每擴展一個類時,都需要改變工廠類里的方法,這就違背了開放-封閉原則。于是工廠方法模式來了:
??工廠方法模式(Factory Method),定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類,工廠方法使一個類的實例化延遲到其子類。 繼續(xù)上一個計算器的例子,簡單工廠模式由工廠類直接生成相應(yīng)的運算類對象,判斷的邏輯在工廠類中,而工廠方法模式的實現(xiàn)則是定義一個工廠接口,然后每個運算類都對應(yīng)一個工廠類來創(chuàng)建,然后在客戶端判斷使用哪個工廠類來創(chuàng)建運算類。

/**
 * @Description: 工廠的接口
 * @author: zxt
 * @time: 2019年2月21日 下午2:49:43
 */
public interface IFactory {
    
    public Operation createOperation();
}

/**
 * @Description: 加法類工廠
 */
public class AddFactory implements IFactory {
    @Override
    public AddOperation createOperation() {
        return new AddOperation();
    }
}

/**
 * @Description: 減法類工廠
 */
public class SubFactory implements IFactory {
    @Override
    public SubOperation createOperation() {
        return new SubOperation();
    }
}

/**
 * @Description: 乘法類工廠
 */
public class MulFactory implements IFactory {
    @Override
    public MulOperation createOperation() {
        return new MulOperation();
    }
}

/**
 * @Description: 除法類工廠
 */
public class DivFactory implements IFactory {

    @Override
    public DivOperation createOperation() {
        return new DivOperation();
    }
}

??工廠方法模式實現(xiàn)時,客戶端需要決定實例化哪一個工廠來實現(xiàn)運算類,選擇判斷的問題還是存在的,也就是說,工廠方法把簡單工廠的內(nèi)部邏輯判斷移到了客戶端代碼來進(jìn)行。你想要加功能,本來是改工廠類的,而現(xiàn)在是修改客戶端。

/**
 * @Description: 實現(xiàn)一個簡單的計算器功能,使用工廠方法模式
 * @author: zxt
 * @time: 2018年7月6日 上午10:11:50
 */
public class Computer {

    private static Scanner scanner;
    
    public static void main(String[] args) {
        scanner = new Scanner(System.in);
        
        System.out.print("請輸入第一個數(shù)字:");
        int firstNum = scanner.nextInt();
        
        System.out.print("請輸入第二個數(shù)字:");
        int secondNum = scanner.nextInt();
        
        System.out.print("請輸入運算符:");
        String operation = scanner.next();
        
        IFactory operFactory = null;
        if(operation.equals("+")) {
            operFactory = new AddFactory();
        } else if(operation.equals("-")) {
            operFactory = new SubFactory();
        } else if(operation.equals("*")) {
            operFactory = new MulFactory();
        } else if(operation.equals("/")){
            operFactory = new DivFactory();
        }
        Operation oper = operFactory.createOperation();
        double result = oper.getResult(firstNum, secondNum);
        System.out.println("result = " + result);
    }
}

??增加新功能時,工廠方法模式比簡單工廠模式修改的代碼量更小,工廠方法克服了簡單工廠違背開放封閉原則的缺點,又保持了封裝對象創(chuàng)建過程的優(yōu)點。但是工廠方法的缺點就是每加一個產(chǎn)品,就需要加一個產(chǎn)品工廠的類,增加了額外的開發(fā)量。當(dāng)然這兩種模式都還不是最佳的做法。

3、抽象工廠模式

??抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和最具一般性的一種形態(tài)。抽象工廠模式是指當(dāng)有多個抽象角色時,使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個接口,使客戶端在不必指定產(chǎn)品的具體的情況下,創(chuàng)建多個產(chǎn)品族中的產(chǎn)品對象。根據(jù)里氏替換原則,任何接受父類型的地方,都應(yīng)當(dāng)能夠接受子類型。因此,實際上系統(tǒng)所需要的,僅僅是類型與這些抽象產(chǎn)品角色相同的一些實例,而不是這些抽象產(chǎn)品的實例。換言之,也就是這些抽象產(chǎn)品的具體子類的實例。工廠類負(fù)責(zé)創(chuàng)建抽象產(chǎn)品的具體子類的實例。
??抽象工廠模式(Abstract Factory):提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定他們具體的類。
??實例場景:對數(shù)據(jù)庫(各種不同的數(shù)據(jù)庫)中的表進(jìn)行修改,此時,使用工廠模式結(jié)構(gòu)圖如下:

1、User表的定義:

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2、定義一個對User表進(jìn)行操作的接口:

/**
 * @Description: 對User類操作的接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:00:20
 */
public interface IUser {
    
    void insert(User user);
    
    User getUser(int id);
}

3、實現(xiàn)Sql Server數(shù)據(jù)庫對User表的操作:

/**
 * @Description:  SQL Server數(shù)據(jù)庫中對User表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:04:39
 */
public class SqlServerUser implements IUser {
    @Override
    public void insert(User user) {
        System.out.println("在  SQL Server 中給 User 表增加一條記錄!");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在  SQL Server 中根據(jù)ID得到  User 表的一條記錄!");
        return null;
    }
}

實現(xiàn)Oracle數(shù)據(jù)庫對User表的操作:

/**
 * @Description: Oracle數(shù)據(jù)庫中對User表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:05:07
 */
public class OracleUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在  Oracle 中給 User 表增加一條記錄!");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在 Oracle 中根據(jù)ID得到  User 表的一條記錄!");
        return null;
    }
}

4、定義一個抽象工廠接口,用于生成對User表的操作的對象:

/**
 * @Description: 得到對User表操作的IUser對象的抽象工廠接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:06:37
 */
public interface IFactory {
    
    public IUser createUser();
}

5、SQLServerFactory工廠用于生成操作Sql Server數(shù)據(jù)庫的SqlServerUser對象:

public class SQLServerFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

OracleFactory工廠用于生成操作Oracle數(shù)據(jù)庫的OracleUser對象:

public class OracleFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new OracleUser();
    }
}

6、客戶端的使用:

public class FactoryMethodTest {

    public static void main(String[] args) {
        User user = new User();
        
        // 若要改成Oracle數(shù)據(jù)庫,只需要將這句改成OracleFactory即可
        IFactory ifactory = new SQLServerFactory();
        
        IUser iu = ifactory.createUser();
        
        iu.insert(user);
        iu.getUser(1);
    }
}


??到此為止,工廠模式都可以很好的解決,由于多態(tài)的關(guān)系,IFactory在聲明對象之前都不知道在訪問哪個數(shù)據(jù)庫,卻可以在運行時很好的完成任務(wù),這就是業(yè)務(wù)邏輯與數(shù)據(jù)訪問的解耦。
??但是,當(dāng)數(shù)據(jù)庫中不止一個表的時候該怎么解決問題呢,此時就可以引入抽象工廠模式了,結(jié)構(gòu)圖如下:


例如增加了部門表Department:

public class Department {
    private int id;
    private String deptName;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
}

則增加相應(yīng)的對Department表操作的接口:

public interface IDepartment {
    
    public void insert(Department department);
    
    public Department getDepartment(int id);
}

實現(xiàn)Sql Server數(shù)據(jù)庫對Department表的操作:

/**
 * @Description:  SQL Server數(shù)據(jù)庫中對Department表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:04:39
 */
public class SqlServerDepartment implements IDepartment {
    @Override
    public void insert(Department user) {
        System.out.println("在  SQL Server 中給 Department 表增加一條記錄!");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在  SQL Server 中根據(jù)ID得到  Department 表的一條記錄!");
        return null;
    }
}

實現(xiàn)Oracle數(shù)據(jù)庫對Department表的操作:

/**
 * @Description: Oracle數(shù)據(jù)庫中對Department表的操作
 * @author: zxt
 * @time: 2019年2月24日 下午7:05:07
 */
public class OracleDepartment implements IDepartment {
    @Override
    public void insert(Department user) {
        System.out.println("在  Oracle 中給 Department 表增加一條記錄!");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在 Oracle 中根據(jù)ID得到  Department 表的一條記錄!");
        return null;
    }
}

IFactory抽象工廠中增加生成對Department表操作的對象:

/**
 * @Description: 得到對User表操作的IUser對象的抽象工廠接口
 * @author: zxt
 * @time: 2019年2月24日 下午7:06:37
 */
public interface IFactory {
    
    public IUser createUser();
    
    public IDepartment createDepartment();
}

SQLServerFactory工廠增加生成操作Sql Server數(shù)據(jù)庫的SqlServerDepartment對象:

public class SQLServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment();
    }
}

OracleFactory工廠增加生成操作Oracle數(shù)據(jù)庫的OracleDepartment對象:

public class OracleFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new OracleUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new OracleDepartment();
    }
}

客戶端的使用:

public class AbstractFactoryTest {

    public static void main(String[] args) {
        User user = new User();
        Department department = new Department();
        
        // 若要改成SQL Server數(shù)據(jù)庫,只需要將這句改成SqlServerFactory即可
        IFactory ifactory = new OracleFactory();
        
        IUser iu = ifactory.createUser();
        iu.insert(user);
        iu.getUser(1);
        
        IDepartment id = ifactory.createDepartment();
        id.insert(department);
        id.getDepartment(1);
    }
}



??所以抽象工廠與工廠方法模式的區(qū)別在于:抽象工廠是可以生產(chǎn)多個產(chǎn)品的,例如OracleFactory 里可以生產(chǎn) OracleUser以及 OracleDepartment兩個產(chǎn)品,而這兩個產(chǎn)品又是屬于一個系列的,因為它們都是屬于Oracle數(shù)據(jù)庫的表。而工廠方法模式則只能生產(chǎn)一個產(chǎn)品,例如之前的 OracleFactory里就只可以生產(chǎn)一個 OracleUser產(chǎn)品。


??抽象工廠模式的優(yōu)缺點:
??優(yōu)點:
??1、抽象工廠模式最大的好處是易于交換產(chǎn)品系列,由于具體工廠類,例如 IFactory factory = new OracleFactory(); 在一個應(yīng)用中只需要在初始化的時候出現(xiàn)一次,這就使得改變一個應(yīng)用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產(chǎn)品配置。不管是任何人的設(shè)計都無法去完全防止需求的更改,或者項目的維護(hù),那么我們的理想便是讓改動變得最小、最容易。
??2、抽象工廠模式的另一個好處就是它讓具體的創(chuàng)建實例過程與客戶端分離,客戶端是通過它們的抽象接口操作實例,產(chǎn)品實現(xiàn)類的具體類名也被具體的工廠實現(xiàn)類分離,不會出現(xiàn)在客戶端代碼中。就像我們上面的例子,客戶端只認(rèn)識IUser和IDepartment,至于它是Sql Server里的表還是Oracle里的表就不知道了。
??缺點:
??1、如果你的需求來自增加功能,比如增加Department表,就有點太煩了。首先需要增加IDepartment,SQLServerDepartment,OracleDepartment。然后我們還要去修改工廠類:IFactory,SQLServerFactory,OracleFactory才可以實現(xiàn),需要修改三個類,實在是有點麻煩。
??2、還有就是,客戶端程序肯定不止一個,每次都需要聲明IFactory factory = new OracleFactory(),如果有100個調(diào)用數(shù)據(jù)庫的類,就需要更改100次IFactory factory = new OracleFactory()。

3.1、抽象工廠模式的改進(jìn)1(簡單工廠+抽象工廠)

??我們將IFactory,SQLServerFactory,OracleFactory三個工廠類都拋棄掉,取而代之的是一個簡單工廠類EasyFactory,如下:

public class EasyFactory {

    private static String db = "SqlServer";
    // private static String db = "Oracle";

    public static IUser createUser() {
        IUser result = null;
        switch (db) {
        case "SqlServer":
            result = new SqlServerUser();
            break;
        case "Oracle":
            result = new OracleUser();
            break;
        }

        return result;
    }

    public static IDepartment createDepartment() {
        IDepartment result = null;
        switch (db) {
        case "SqlServer":
            result = new SqlServerDepartment();
            break;
        case "Oracle":
            result = new OracleDepartment();
            break;
        }

        return result;
    }
}

客戶端:

public class EasyClient {

    public static void main(String[] args) {
        User user = new User();
        Department department = new Department();

        // 直接得到實際的數(shù)據(jù)庫訪問實例,而不存在任何依賴
        IUser userOperation = EasyFactory.createUser();

        userOperation.getUser(1);
        userOperation.insert(user);

        // 直接得到實際的數(shù)據(jù)庫訪問實例,而不存在任何依賴
        IDepartment departmentOperation = EasyFactory.createDepartment();

        departmentOperation.insert(department);
        departmentOperation.getDepartment(1);
    }
}

??由于事先在簡單工廠類里設(shè)置好了db的值,所以簡單工廠的方法都不需要由客戶端來輸入?yún)?shù),這樣在客戶端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createDepartment(); 方法來獲得具體的數(shù)據(jù)庫訪問類的實例,客戶端代碼上沒有出現(xiàn)任何一個 SqlServer 或 Oracle 的字樣,達(dá)到了解耦的目的,客戶端已經(jīng)不再受改動數(shù)據(jù)庫訪問的影響了。

3.2、抽象工廠的改進(jìn)2(反射+簡單工廠)

??使用反射的話,我們就可以不需要使用switch,因為使用switch的話,我添加一個Mysql數(shù)據(jù)庫的話,又要switch的話又需要添加case條件。
??我們可以根據(jù)選擇的數(shù)據(jù)庫名稱,如“mysql”,利用反射技術(shù)自動的獲得所需要的實例:

public class EasyFactoryReflect {

    private static String packName = "com.zxt.abstractfactory";
    private static String sqlName = "Oracle";

    public static IUser createUser() throws Exception {
        String className = packName + "." + sqlName + "User";
        return (IUser) Class.forName(className).newInstance();
    }

    public static IDepartment createLogin() throws Exception {
        String className = packName + "." + sqlName + "Department";
        return (IDepartment) Class.forName(className).newInstance();
    }
}

??以上我們使用簡單工廠模式設(shè)計的代碼中,是用一個字符串類型的db變量來存儲數(shù)據(jù)庫名稱的,所以變量的值到底是 SqlServer 還是 Oracle,完全可以由事先設(shè)置的那個db變量來決定,而我們又可以通過反射來去獲取實例,這樣就可以去除switch語句了。

3.3、抽象工廠的改進(jìn)3(反射+配置文件+簡單工廠)

??在使用反射之后,我們還是需要進(jìn)EasyFactory中修改數(shù)據(jù)庫類型,還不是完全符合開-閉原則。我們可以通過配置文件來達(dá)到目的,每次通過讀取配置文件來知道我們應(yīng)該使用哪種數(shù)據(jù)庫。
??如下是一個json類型的配置文件,也可以使用xml類型的配置文件:

{
    "packName": " com.zxt.abstractfactory",
    "DB": "Oracle"
}

??之后就可以通過這個配置文件去找需要加載的類是哪一個。我們通過反射機制+配置文件+簡單工廠模式解決了數(shù)據(jù)庫訪問時的可維護(hù)、可擴展的問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計模式匯總 一、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,095評論 1 15
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,514評論 6 13
  • 設(shè)計模式基本原則 開放-封閉原則(OCP),是說軟件實體(類、模塊、函數(shù)等等)應(yīng)該可以拓展,但是不可修改。開-閉原...
    西山薄涼閱讀 4,086評論 3 14
  • 鏈接:https://github.com/WiKi123/DesignPattern作者: WiKi123(gi...
    樹懶啊樹懶閱讀 3,856評論 0 2
  • 不記得有多久沒去過清吧酒吧活動了,今晚去聽歌的時候,有一種找回了年輕的記憶感覺,也曾經(jīng)跟好友定期去玩,那時感覺無憂...
    圓圓_04b9閱讀 285評論 0 0

友情鏈接更多精彩內(nèi)容