1. 程式人生 > >大話設計模式之抽象工廠模式總結-java實現

大話設計模式之抽象工廠模式總結-java實現

注:示例來自《大話設計模式》

現有如下需求 寫一個基本的資料訪問程式 資料庫用SqlServer 簡單程式碼實現如下

使用者類

package Test15;

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; } }

操作表類

package Test15;

public class SqlserverUser {

    public void Insert(User user)
    {
        System.out.println("在Sqlserver中給User表增加一條記錄");
    }

    public User GetUser(int id)
    {
        System.out
.println("在Sqlserver中根據ID得到User表一條記錄"); return null; } }

客戶端程式碼

package Test15;

public class Program {

    public static void main(String[] args) {

        User user = new User();

        SqlserverUser su = new SqlserverUser();
        su.Insert(user);
        su.GetUser(1);

    }

}

上面的寫法 客戶端與SqlServer強耦合 如果想要更換資料庫為Access 該怎麼辦呢
下面使用工廠方法模式進行重構 程式碼如下

IUser介面

package Test15;

public interface IUser {

    void Insert(User user);

    User GetUser(int id);

}

SqlserverUser類

package Test15;

public class SqlserverUser implements IUser {

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

    @Override
    public User GetUser(int id)
    {
        System.out.println("在Sqlserver中根據ID得到User表一條記錄");
        return null;
    }

}

AccessUser類

package Test15;

public class AccessUser implements IUser {

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

    @Override
    public User GetUser(int id)
    {
        System.out.println("在Access中根據ID得到User表一條記錄");
        return null;
    }

}

IFactory介面

package Test15;

public interface IFactory {

    IUser CreateUser();

}

SqlServerFactory類

package Test15;

public class SqlServerFactory implements IFactory {

    @Override
    public IUser CreateUser() {
        return new SqlserverUser();
    }

}

AccessFactory類

package Test15;

public class AccessFactory implements IFactory {

    @Override
    public IUser CreateUser() {
        return new AccessUser();
    }

}

客戶端程式碼

package Test15;

public class Program {

    public static void main(String[] args) {

        User user = new User();
        //IFactory factory = new SqlServerFactory();
        IFactory factory = new AccessFactory();

        IUser iu = factory.CreateUser();

        iu.Insert(user);
        iu.GetUser(1);

    }

}

現在如果要換資料庫 只需要把new SqlServerFactory()改成new AccessFactory()
但是問題還沒有完全解決 資料庫裡不可能只有一個User表 很可能有其他表 比如增加部門表 此時該怎麼辦呢 修改程式碼如下

部門類

package Test15;

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;
    }

}

IDepartment介面

package Test15;

public interface IDepartment {

    void Insert(Department department);

    Department GetDepartment(int id);

}

SqlserverDepartment類

package Test15;

public class SqlserverDepartment implements IDepartment {

    public void Insert(Department department)
    {
        System.out.println("在Sqlserver中給Department表增加一條記錄");
    }

    public Department GetDepartment(int id)
    {
        System.out.println("在Sqlserver中根據ID得到Department表一條記錄");
        return null;
    }

}

AccessDepartment類

package Test15;

public class AccessDepartment implements IDepartment {

    public void Insert(Department department)
    {
        System.out.println("在Access中給Department表增加一條記錄");
    }

    public Department GetDepartment(int id)
    {
        System.out.println("在Access中根據ID得到Department表一條記錄");
        return null;
    }

}

IFactory介面

package Test15;

public interface IFactory {

    IUser CreateUser();

    IDepartment CreateDepartment();

}

SqlServerFactory類

package Test15;

public class SqlServerFactory implements IFactory {

    @Override
    public IUser CreateUser() {
        return new SqlserverUser();
    }

    @Override
    public IDepartment CreateDepartment()
    {
        return new SqlserverDepartment();
    }

}

AccessFactory類

package Test15;

public class AccessFactory implements IFactory {

    @Override
    public IUser CreateUser() {
        return new AccessUser();
    }

    @Override
    public IDepartment CreateDepartment()
    {
        return new AccessDepartment();
    }

}

客戶端程式碼

package Test15;

public class Program {

    public static void main(String[] args) {

        User user = new User();
        Department dept = new Department();

        //IFactory factory = new SqlServerFactory();
        IFactory factory = new AccessFactory();
        IUser iu = factory.CreateUser();

        iu.Insert(user);
        iu.GetUser(1);

        IDepartment id = factory.CreateDepartment();
        id.Insert(dept);
        id.GetDepartment(1);

    }

}

不知不覺間 我們已經重構出了抽象工廠模式

抽象工廠模式 提供一個建立一系列相關或相互依賴物件的介面 而無需指定它們具體的類

抽象工廠模式最大的好處便是易於交換產品系列 由於具體工廠類 例如IFactory factory = new AccessFactory() 在一個應用中只需要在初始化的時候出現一次 這就使得改變一個應用的具體工廠變得非常容易 它只需要改變具體工廠即可使用不同的產品配置
第二大好處是 它讓具體的建立例項過程與客戶端分離 客戶端是通過它們的抽象介面操縱例項 產品的具體類名也被具體工廠的實現分離 不會出現在客戶程式碼中

缺點 如果需要增加新的表 那麼就要新增三個類 並且要修改三個工廠類 這樣大批量的改動 顯然是非常醜陋的做法

下面我們用簡單工廠來改進抽象工廠 程式碼如下

DataAccess類

package Test15;

public class DataAccess {

    private static final String db = "Sqlserver";
    //private static final String db = "Access";

    public static IUser CreateUser()
    {
        IUser result = null;
        switch (db)
        {
            case "Sqlserver":
                result = new SqlserverUser();
                break;
            case "Access":
                result = new AccessUser();
                break;
        }
        return result;
    }

    public static IDepartment CreateDepartment()
    {
        IDepartment result = null;
        switch (db)
        {
            case "Sqlserver":
                result = new SqlserverDepartment();
                break;
            case "Access":
                result = new AccessDepartment();
                break;
        }
        return result;
    }

}

客戶端程式碼

package Test15;

public class Program {

    public static void main(String[] args) {

        User user = new User();
        Department dept = new Department();

        IUser iu = DataAccess.CreateUser();

        iu.Insert(user);
        iu.GetUser(1);

        IDepartment id = DataAccess.CreateDepartment();
        id.Insert(dept);
        id.GetDepartment(1);

    }

}

現在客戶端已經不再受改動資料庫訪問的影響了 但是如果我需要增加Oracle資料庫訪問 本來抽象工廠只增加一個OracleFactory工廠類就可以了 現在就比較麻煩了
下面我們用反射+抽象工廠的方式進行重構 程式碼如下

修改DataAccess類

package Test15;

public class DataAccess {

    private static final String AssemblyName = "Test15";
    private static final String db = "Sqlserver";
    //private static final String db = "Access";

    public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        String className = AssemblyName+"."+db+"User";
        Class<IUser> forName = (Class<IUser>) Class.forName(className); 
        return forName.newInstance();
    }

    public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        String className = AssemblyName+"."+db+"Department";
        Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className); 
        return forName.newInstance();
    }

}

現在如果增加Oracle資料訪問 我們只需更改private static final String db = “Sqlserver”;為private static final String db = “Oracle”;
但是 總感覺還是有點遺憾 因為在更換資料庫訪問時 我還是需要去改程式重編譯
下面我們用反射+配置檔案的方式進行重構

新增一個sql.properties檔案 內容如下

db=Sqlserver

DataAccess類

package Test15;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class DataAccess {

    private static final String AssemblyName = "Test15";

    public static String getDb() throws IOException {

        Properties properties = new Properties();
        String db = "";
        // 使用BufferedReader載入properties配置檔案生成對應的輸入流
        BufferedReader bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.dir")+"/src/test/java/Test15/sql.properties"));
        // 使用properties物件載入輸入流
        properties.load(bufferedReader);
        //獲取key對應的value值
        db = properties.getProperty("db");
        return db;

    }

    public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
    {   
        String className = AssemblyName+"."+getDb()+"User";
        Class<IUser> forName = (Class<IUser>) Class.forName(className);
        return forName.newInstance();
    }

    public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
    {
        String className = AssemblyName+"."+getDb()+"Department";
        Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className); 
        return forName.newInstance();
    }

}

現在如果需要更換資料庫 只需要修改配置檔案就可以了 從這個角度上說 所有在用簡單工廠的地方 都可以考慮用反射技術來去除switch或if 解除分支判斷帶來的耦合