設計模式 | 抽象工廠模式(abstract factory)

定義:

提供一個建立一系列相關或相互依賴對象的接口,而無需指定他們具體的類。

結構:(書中圖,侵刪)

這個圖相對來講有一點點複雜,其實就是在工廠方法模式的基礎上作了一些擴展,工廠方法模式只用於生成一種產品(把上圖ProductB相關的都去掉就是了),而抽象工廠模式可用於生產多種產品。
加上例子吧,假設生產海賊的手辦(路飛和娜美)。
一個抽象工廠抽象接口(包含生成全部類型產品的方法,即生成路飛和娜美的方法)
若干個具體工廠(各類生成產品的不一樣實現的工廠,理論上,同一個具體工廠底下生成的都是同一個系列的產品。相似於A工廠生成兩年前的,B工廠生成兩年後的,生成出來的都是同一我的物)
若干個抽象的產品接口(這裏就是路飛和娜美兩個)
每一個抽象的產品接口下有若干個具體的產品類(路飛下有(兩年前路飛、兩年後路飛);娜美下有(兩年前娜美,兩年後娜美))
根據上例照着原格式再來畫張圖,便於理解:(把client去掉了,懶得畫)

實例:

鑑於書中的例子至關的常見,因此決定延用書中的例子。
就是更換數據庫的例子。
假設系統中有員工、部門兩個類。
而後系統須要使用mysql和oracle兩個數據庫。
爲了代碼簡潔,不分什麼dao層之類的,直接把調用數據庫的方法寫在實體裏。
員工抽象類:
package designpattern.abstractfactory;

public abstract class Employee {
    private String name;

    abstract void insert(Employee employee);

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Employee [name=" + name + "]";
    }

}
oracle員工類:
package designpattern.abstractfactory;

public class OracleEmployee extends Employee {

    @Override
    void insert(Employee employee) {
        System.out.println("往oracle數據庫插入一條Employee員工數據:" + employee);
    }

}
mysql員工類:
package designpattern.abstractfactory;

public class MysqlEmployee extends Employee {
    @Override
    public void insert(Employee employee) {
        System.out.println("往mysql數據庫插入一條Employee員工數據:" + employee);
    }

}
部門抽象類:
package designpattern.abstractfactory;

public abstract class Department {
    String name;

    abstract void insert(Department department);

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Department [name=" + name + "]";
    }

}
oracle部門類:
package designpattern.abstractfactory;

public class OracleDepartment extends Department {

    @Override
    void insert(Department department) {
        System.out.println("往oracle數據庫插入一條Department部門數據:" + department);
    }

}
mysql部門類:
package designpattern.abstractfactory;

public class MysqlDepartment extends Department {

    @Override
    void insert(Department department) {
        System.out.println("往mysql數據庫插入一條Department部門數據:"+department);
    }

}
抽象工廠類:
package designpattern.abstractfactory;

public interface Factory {
    Employee createEmployee();

    Department createDepartment();
}
mysql工廠類:
package designpattern.abstractfactory;

public class MysqlFactory implements Factory {

    @Override
    public Employee createEmployee() {
        return new MysqlEmployee();
    }

    @Override
    public Department createDepartment() {
        return new MysqlDepartment();
    }

}
oracle工廠類:
package designpattern.abstractfactory;

public class OracleFactory implements Factory {

    @Override
    public Employee createEmployee() {
        return new OracleEmployee();
    }

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

}
客戶端:
package designpattern.abstractfactory;

public class Client {
    public static void main(String[] args) {
        Factory factory = new MysqlFactory();
        // Factory factory=new OracleFactory();

        Employee employee = factory.createEmployee();
        employee.setName("張三");
        employee.insert(employee);

        Department department = factory.createDepartment();
        department.setName("技術部");
        department.insert(department);

    }
}
結果輸出:
往mysql數據庫插入一條Employee員工數據:Employee [name=張三]
往mysql數據庫插入一條Department部門數據:Department [name=技術部]

 

這個設計模式很好的解除了客戶端與實例建立過程的耦合,經過抽象出接口的方式,使客戶端只須要和接口打交道。
同時也使得切換數據庫變得容易,只須要修改初始化的語句便可。
這一樣也是這個模式的不足之處,意味着全部須要用到數據庫鏈接的地方都要寫上這句初始化語句,使得修改的工做量變得很大。
 
接下來就一步一步優化它:
首先,使用哪一個數據庫的判斷是在客戶端,咱們須要把這個判斷轉移,使用簡單工廠模式,將判斷轉移至簡單工廠:
簡單工廠:
package designpattern.abstractfactory;

public class SimpleFactory {
    static String db = "mysql";
    //static String db="oracle";

    static Employee createEmployee() {
        switch (db) {
        case "mysql":
            return new MysqlEmployee();
        case "oracle":
            return new OracleEmployee();
        default:
            return null;
        }
    }

    static Department createDepartment() {
        switch (db) {
        case "mysql":
            return new MysqlDepartment();
        case "oracle":
            return new OracleDepartment();
        default:
            return null;
        }
    }

}
客戶端:
package designpattern.abstractfactory;

public class Client2 {
    public static void main(String[] args) {
        Employee employee = SimpleFactory.createEmployee();
        employee.setName("張三");
        employee.insert(employee);

        Department department = SimpleFactory.createDepartment();
        department.setName("技術部");
        department.insert(department);

    }
}

 

而後,若是再增長一個數據庫,須要在全部的方法裏增長switch的case,這也是很麻煩的事情,這裏須要用到反射來解決這個問題:
反射版簡單工廠:
package designpattern.abstractfactory;

public class ReflectSimpleFactory {
    static String db = "Mysql";
    // static String db="Oracle";

    static String path = "designpattern.abstractfactory";// 包路徑

    static Employee createEmployee() {
        try {
            Class<Employee> employee = (Class<Employee>) Class.forName(path + "." + db + "Employee");
            return employee.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    static Department createDepartment() {
        try {
            Class<Department> department = (Class<Department>) Class.forName(path + "." + db + "Department");
            return department.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

}
客戶端:
package designpattern.abstractfactory;

public class Client3 {
    public static void main(String[] args) {
        Employee employee = ReflectSimpleFactory.createEmployee();
        employee.setName("張三");
        employee.insert(employee);

        Department department = ReflectSimpleFactory.createDepartment();
        department.setName("技術部");
        department.insert(department);

    }
}
經過反射,將程序由編譯時改成運行時,完全取代了switch語句。
 
如今,還剩最後一個問題,決定使用什麼數據庫的字符串仍是寫在代碼中,修改以後還須要從新編譯,這裏只須要把字符串改到配置文件中便可:
簡單工廠:
package designpattern.abstractfactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ReflectSimpleFactory2 {

    static String path = "designpattern.abstractfactory";// 包路徑

    static Employee createEmployee() {
        try {
            Class<Employee> employee = (Class<Employee>) Class.forName(path + "." + getDBName() + "Employee");
            return employee.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    static Department createDepartment() {
        try {
            Class<Department> department = (Class<Department>) Class.forName(path + "." + getDBName() + "Department");
            return department.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getDBName() {
        String dbName = null;
        try {
            InputStream in = ReflectSimpleFactory2.class.getResourceAsStream("db.properties");
            Properties pro = new Properties();
            pro.load(in);
            in.close();
            dbName = pro.getProperty("db");
        } catch (IOException e) {
            e.printStackTrace();
        }

        return dbName;

    }
}
配置文件:
db=Mysql
#db=Oracle
客戶端:
package designpattern.abstractfactory;

public class Client4 {
    public static void main(String[] args) {
        Employee employee = ReflectSimpleFactory2.createEmployee();
        employee.setName("張三");
        employee.insert(employee);

        Department department = ReflectSimpleFactory2.createDepartment();
        department.setName("技術部");
        department.insert(department);

    }
}
大功告成!
 

總結:

抽象工廠設計模式和其餘的工廠類設計模式同樣,就是將客戶端與具體的對象建立過程分離。
只不過這裏所涉及到的再也不是一種類,而是多種類,結構相對複雜。
同時也像上文說的同樣,存在一些不足,能夠具體狀況具體分析,應該如何使用。
相關文章
相關標籤/搜索