1. 程式人生 > 其它 >設計模式----抽象工廠模式

設計模式----抽象工廠模式

今天我們來介紹一種新的設計模式----抽象工廠模式。

大家可以複習一下之前學過的簡單工廠模式和工廠方法模式:簡單工廠模式 工廠方法模式

為了讓大家理解的更加深刻,我先來舉一個例子:

我們要幫一個公司寫一個連線資料庫以及配置操作的程式,這個公司一開始用的Oracel,後來又用了mysql,之後為了省錢乾脆用Access當做資料庫。

(這個例子我是瞎舉的,現實中應該沒有這樣的公司)

這個公司總共換了兩次資料庫,我們就要改兩次程式碼(因為不同的資料庫語句不同並且可能有的方法也不同)

如果我們就是很簡單的寫最基本的資料訪問程式的話,要修改起來是很麻煩的一件事,因為要在程式的很多地方動刀子,那麼這樣的程式就是醜陋的程式,

為了讓我們的程式碼更加優美簡潔,我們就有必要使用今天介紹的 抽象工廠模式

先來看看抽象工廠模式的UML類圖

看上去有點複雜,其實非常好理解:

首先有一個抽象工廠的介面,要實現的介面方法是產生不同的產品

為了生產同種產品的不同形式(好比都是對接資料庫的程式,但是資料庫又有MySQL,Oracle等等......)

而AbstractProductA/B則是不同的產品(好比資料庫訪問使用者表或者訪問部門表)

繼承它的子類ConcreateProduct(A/B)(1/2)則是不同資料庫的不同產品。

我們再來看看程式碼:

Template:

抽象工廠程式碼:

public abstract class
IFactory { public abstract IProductA createProductA(); public abstract IProductB createProductB(); }

具體工廠1:

class ConcreteFactory1 extends IFactory{
    @Override
    public IProductA createProductA() {
        System.out.println("生產ProductA1");
        return new ProductA1();
    }

    @Override
    
public IProductB createProductB() { System.out.println("生產ProductB1"); return new ProductB1(); } }

具體工廠2:

class ConcreteFactory2 extends IFactory{
    @Override
    public IProductA createProductA() {
        System.out.println("生產ProductA2");
        return new ProductA2();
    }

    @Override
    public IProductB createProductB() {
        System.out.println("生產ProductB2");
        return new ProductB2();
    }
}

具體產品類我就不寫了。

看看Client:

public static void main(String[] args) {
        IFactory iFactory1 = new ConcreteFactory1();
        iFactory1.createProductA();
        iFactory1.createProductB();

        IFactory iFactory2 = new ConcreteFactory2();
        iFactory2.createProductA();
        iFactory2.createProductB();
    }

控制檯輸出:

生產ProductA1
生產ProductB1
生產ProductA2
生產ProductB2

這也體現了對多型的靈活運用。

我們再來看一個具體的例子:

Example:

這個例子就是之前的那個省錢的公司,我們要幫這個公司改寫我們之前醜陋的程式碼,運用抽象工廠模式實現對資料庫MySQL或者Access的訪問:

先來看看UML類圖:

先來看看抽象工廠:

/**
 * @author 陳柏宇
 * 抽象工廠介面
 */

public interface IFactory {
    IUser createUser();
    IDepartment createDepartment();
}

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

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

class AccessFactory implements IFactory{
    @Override
    public IUser createUser() {
        return new AccessUser();
    }

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

看看IUser(對User的操作):

public interface IUser {
    void insert(User user);
    User getUserById(int id);
}

class SqlServerUser implements IUser{
    @Override
    public void insert(User user) {
        System.out.println("SQLServer插入一條User資料");
    }

    @Override
    public User getUserById(int id) {
        System.out.println("SQLServer根據id查詢一條使用者資料");
        return null;
    }
}

class AccessUser implements IUser{
    @Override
    public void insert(User user) {
        System.out.println("Access插入一條User資料");
    }

    @Override
    public User getUserById(int id) {
        System.out.println("Access根據id查詢一條使用者資料");
        return null;
    }
}

看看IDepartment(對Department的操作):

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

class SqlServerDepartment implements IDepartment{
    @Override
    public void insert(Department department) {
        System.out.println("SQLServer插入一條Department資料");
    }

    @Override
    public Department getDepartmentById(int id) {
        System.out.println("SQLServer根據id查詢一條Department資料");
        return null;
    }
}

class AccessDepartment implements IDepartment{
    @Override
    public void insert(Department department) {
        System.out.println("Access插入一條Department資料");
    }

    @Override
    public Department getDepartmentById(int id) {
        System.out.println("Access根據id查詢一條Department資料");
        return null;
    }
}

Client端:

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

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

        IDepartment iDepartment = factory.createDepartment();
        IUser iUser = factory.createUser();

        iDepartment.insert(department);
        iDepartment.getDepartmentById(1);

        iUser.insert(user);
        iUser.getUserById(1);
    }

如果客戶端是這樣的程式碼那麼我們可以控制:

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

來控制我們到底是操縱MySQL資料庫還是Access

如果客戶端就是按照這樣的那麼輸出:

SQLServer插入一條Department資料
SQLServer根據id查詢一條Department資料
SQLServer插入一條User資料
SQLServer根據id查詢一條使用者資料

否則輸出:

Access插入一條Department資料
Access根據id查詢一條Department資料
Access插入一條User資料
Access根據id查詢一條使用者資料

這樣就大大化簡了我們的操作。

但是這樣就結束了嗎?

事情還沒有結束......

Reinforce:

抽象工廠模式它並不是十全十美的,還是優缺點的,如果這裡我們想加入一個專案表Project

那麼我們至少要新增三個類:IProject、SqlServerProject、AccessProject

還需要修改IFactory、SqlServerFactory、AccessFactory才可以完全實現

這實在是有點糟糕。

而且我們客戶端程式不會只有一個,很多地方都在使用IUser,IDepartmen,這樣的設計我們就要在實現工廠的地方都要修改,

有100處就修改100次,程式設計是門藝術,這樣大批量的改動,顯然是醜陋的做法。

那麼我們還可以用什麼方法來改進抽象工廠呢?

我這裡介紹一種我認為非常好的方法來改進它:用反射+配置檔案實現資料訪問

我們可以通過配置properties檔案 + IO流讀取檔案中的內容獲取我們使用的資料庫型別,然後通過反射確定方法呼叫即可。

properties檔案配置如下:

DB=SqlServer

客戶端這樣寫:

public static void main(String[] args) {
        FileInputStream fis = null;
        String db = null;
        try {
            Properties pros = new Properties();
            fis = new FileInputStream("file.properties");
            pros.load(fis);  //載入對應的流檔案
            //獲取資料庫型別
            db = pros.getProperty("DB");
            //獲取全類名
            String name = "reinforce." + db + "Factory";
            //利用反射根據類名獲取類
            Class<IFactory> clazz = (Class<IFactory>) Class.forName(name);
            //根據類獲取物件
            IFactory iFactory = clazz.newInstance();

            IDepartment department = iFactory.createDepartment();

            department.getDepartmentById(1);
            department.insert(new Department());

            IUser user = iFactory.createUser();
            user.getUserById(1);
            user.insert(new User());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

輸出:

SQLServer根據id查詢一條Department資料
SQLServer插入一條Department資料
SQLServer根據id查詢一條使用者資料
SQLServer插入一條User資料

我們運用了反射獲取到了SqlServerFactory的物件,想要獲取AccessFactory物件只需要修改配置檔案為:

DB=Access

這時候再執行客戶端輸出:

Access根據id查詢一條Department資料
Access插入一條Department資料
Access根據id查詢一條使用者資料
Access插入一條User資料

我們利用反射實現了只用操縱配置檔案就可以實現不同資料庫的訪問。