設計模式詳解(二):工廠方法模式、抽象工廠模式


2. 工廠方法模式

(1)概念

工廠方法模式的定義是:定義一個用於創建對象的接口,讓子類決定實現哪一個類。

即工廠父類負責定義創建產品對象的公共接口,工廠子類負責生成具體的產品對象。

將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。

工廠方法模式是簡單工廠模式的延伸與改進,既繼承了其封裝性等優點,又彌補了其缺陷(提高擴展性),使其符合原則的要求。

在工廠方法模式中,每一個具體工廠只能生產一種具體產品。具體工廠與具體產品一一對應。

(2)類圖、典型代碼

Factory(抽象類或接口):抽象工廠類,聲明了工廠方法,用於返回一個產品。抽象工廠是工廠方法模式的核心,它與應用程序無關。

ConcreteFactory(實現類):具體工廠類,實現抽象工廠類中聲明的方法,可由客戶端調用,返回一個具體產品類的實例。

Product(抽象類或接口):抽象產品類,定義了產品的接口,是產品對象的共同父類或接口。

ConcreteProduct(實現類):具體產品類,實現了抽象產品類的方法,其具體產品由對應的具體工廠創建。

典型代碼如下:

//抽象工廠類
public interface Factory {
    public Product factoryMethod();
}
//具體工廠類之一
public class ConcreteFactory implements Factory {
    @Override
    public Product factoryMethod() {
        return new ConcreteFactory();
    }
}

(3)舉例

原有一個工廠生產一種電視機,現分為兩個子工廠:海爾工廠生產海爾電視機,海信工廠生產海信電視機。

根據工廠方法模式設計類圖如下:

實現代碼如下:

//抽象產品類,定義所有產品必須實現的方法
public interface TV {
    public void play();
}
//具體產品類1--海爾電視
public class HaierTV implements TV {

    @Override
    public void play() {
        // 海爾電視的功能
        System.out.println("海爾電視播放中...");
    }

}
//具體產品類2--海信電視
public class HisenseTV implements TV {

    @Override
    public void play() {
        // 海信電視的功能
        System.out.println("海信電視播放中...");
    }

}
//抽象工廠類,定義所有工廠必須實現的方法
public interface TVFactory {
    public TV produceTV();
}
//具體產品類1--海爾電視
public class HaierTV implements TV {

    @Override
    public void play() {
        // 海爾電視的功能
        System.out.println("海爾電視播放中...");
    }

}
//具體產品類2--海信電視
public class HisenseTV implements TV {

    @Override
    public void play() {
        // 海信電視的功能
        System.out.println("海信電視播放中...");
    }

}
//客戶端調用類--調用具體電視工廠生產對應電視
public class Client {

    public static void main(String[] args) {
        TVFactory factory;
        TV tv;

        // 產生海爾電視並調用其功能
        factory = new HaierTVFactory();
        tv = factory.produceTV();
        tv.play();

        // 產生海信電視並調用其功能
        factory = new HisenseTVFactory();
        tv = factory.produceTV();
        tv.play();
    }

}

輸出結果如下:

海爾電視播放中...
海信電視播放中...

如果此時還需要增加一種電視機品牌TCL,則只需要新建一個實現TV接口的具體產品類和一個實現TVFactory接口的具體工廠類即可,不需要修改其他代碼:

//新增具體產品類3--TCL電視
public class TCLTV implements TV {

    @Override
    public void play() {
        // TCL電視的功能
        System.out.println("TCL電視播放中...");
    }

}
//新增具體工廠類3--專門生產TCL電視的TCL工廠
public class TCLTVFactory implements TVFactory {

    @Override
    public TV produceTV() {
        // 生產一個TCL電視的對象
        return new TCLTV();
    }

}

在客戶端調用類中增加調用TCL工廠生產TCL電視的語句:

        // 產生TCL電視並調用其功能
        factory = new TCLTVFactory();
        tv = factory.produceTV();
        tv.play();

輸出結果如下:

海爾電視播放中...
海信電視播放中...
TCL電視播放中...

(4)優缺點、適用場景

工廠方法模式的優點:

  用戶只需關心所需產品對應的工廠,無須關心創建細節(屏蔽產品類),甚至不需要知道具體產品的類名;

  典型的解耦框架,高層模塊需要知道產品的抽象類,其他的實現類都不用關心;

  良好的封裝性,代碼結構清晰,優秀的擴展性,同時符合開閉原則。

工廠方法模式的缺點:

  在添加新產品時成對增加了類的個數,增加了系統的復雜度,編譯和運行更多的類也會增加系統的開銷;

  考慮到可擴展性引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度。

工廠方法模式的適用場景:

  需要靈活、可擴展的框架時;

  當一個類(比如客戶端類)不知道所需要的對象的類時(需要知道其對應的工廠);

  一個類通過其子類來確定創建那個對象。

 

3. 抽象工廠模式

(1)概念

抽象工廠模式的定義是:為創建一組相關或相互依賴的對象提供一個接口,而且無需指定他們的具體類。

抽象工廠模式是工廠方法模式的泛化版,即工廠方法模式只是抽象工廠模式的一種特殊情況。

在抽象工廠模式中,每一個具體工廠可以生產多個具體產品。

(2)類圖、典型代碼

 

AbstractFactory(抽象類或接口):抽象工廠類,用於聲明生產產品的方法。

ConcreteFactory(實現類):具體工廠類,具體實現生產產品的方法,返回一個具體產品。

AbstractProduct(抽象類或接口):抽象產品類,定義了每種產品的功能方法。

ConcreteProduct(實現類):具體產品類,具體實現了抽象產品類定義的功能方法。

典型代碼如下:

// 抽象工廠類
public interface AbstractFactory {
    public AbstractProductA createProductA();
    public AbstractProductB createProductB();
}
// 具體工廠類之一
public class ConcreteFactory implements AbstractFactory{
    public AbstractProductA createProductA(){
        return new ConcreteProductA();
    }
    public AbstractProductB createProductB(){
        return new ConcreteProductB();
    }
}

(3)舉例

一個電器工廠可以生產多種電器,比如海爾工廠可以生產海爾電視和海爾空調,TCL工廠可以生產TCL電視和TCL空調。

根據抽象工廠模式設計類圖如下:

實現代碼如下:

//抽象產品類1--電視類
public interface TV {
    public void play();
}
//電視具體產品類1--海爾電視
public class HaierTV implements TV {

    @Override
    public void play() {
        System.out.println("海爾電視播放中...");
    }

}
//電視具體產品類2--TCL電視
public class TCLTV implements TV {

    @Override
    public void play() {
        System.out.println("TCL電視播放中...");
    }

}
//抽象產品類2--空調類
public interface AirConditioner {
    public void changeTemperature();
}
//空調具體產品類1--海爾空調
public class HaierAirConditioner implements AirConditioner {

    @Override
    public void changeTemperature() {
        System.out.println("海爾空調吹風中...");
    }

}
//空調具體產品類2--TCL空調
public class TCLAirConditioner implements AirConditioner {

    @Override
    public void changeTemperature() {
        System.out.println("TCL空調吹風中...");
    }

}
//抽象工廠類,定義所有工廠必須實現的方法
public interface Factory {
    public TV produceTV();

    public AirConditioner produceAirConditioner();
}
//具體工廠類1--海爾工廠
public class HaierFactory implements Factory{

    @Override
    public TV produceTV() {
        return new HaierTV();
    }

    @Override
    public AirConditioner produceAirConditioner() {
        return new HaierAirConditioner();
    }

}
//具體工廠類2--TCL工廠
public class TCLFactory implements Factory{

    @Override
    public TV produceTV() {
        return new TCLTV();
    }

    @Override
    public AirConditioner produceAirConditioner() {
        return new TCLAirConditioner();
    }

}

運行結果如下:

海爾電視播放中...
海爾空調吹風中...
———————————————
TCL電視播放中...
TCL空調吹風中...

(4)優缺點、適用場景

抽象工廠模式的優點:

  隔離了具體類的生成,使客戶並不知道什么被創建;

  產品內的約束為非公開狀態(比如不同產品的生產比例,這對調用工廠類的高層模塊是透明的);

抽象工廠模式的缺點:

  在添加新的產品對象時,難以擴展抽象工廠來生產新種類的產品(對接口進行擴展會導致所有子類的修改);

抽象工廠模式的適用場景:

  一個系統不依賴產品類實例如何被創建、組合和表達的細節時;

  系統中有多個產品族,而每次只使用其中某一產品族時;

  屬於同一產品族的產品將一起使用;

  多個對象有相同的約束時。

 

在實際的應用開發中,一般將具體類的類名寫入配置文件中,再通過Java的反射機制讀取XML格式的配置文件,根據存儲在XML文件中的類名字符串生成對象。

新建XML文件config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <className>xxxFactory</className>
</config>

新建工具類文件XMLUtil.java如下:

package com.test.util;

import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLUtil {
    // 該方法用於從XML配置文件中提取類名字符串,並返回一個實例對象
    public static Object getBean() {
        try {
            // 創建DOM文檔對象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc = builder.parse(new File(
                    "./src/com/test/util/config.xml"));// 若不在同一路徑下,必須指定文件具體路徑,用正斜杠/或雙反斜杠\\

            // 獲取包含類名的文本節點
            NodeList nlist = doc.getElementsByTagName("className");
            Node classNode = nlist.item(0).getFirstChild();
            String cName = classNode.getNodeValue();

            // 通過類名生成實例對象並將其返回
            Class c = Class.forName("com.test.factory_method." + cName);// 若不在同一路徑下,必須寫出類的全名
            Object obj = c.newInstance();
            return obj;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

改進工廠方法模式中的客戶端代碼如下:

//客戶端調用類--調用具體電視工廠生產對應電視
public class Client {

    public static void main(String[] args) {
        TVFactory factory;
        TV tv;

//        // 產生海爾電視並調用其功能
//        factory = new HaierTVFactory();
//        tv = factory.produceTV();
//        tv.play();
//
//        // 產生海信電視並調用其功能
//        factory = new HisenseTVFactory();
//        tv = factory.produceTV();
//        tv.play();
//        
//        // 產生TCL電視並調用其功能
//        factory = new TCLTVFactory();
//        tv = factory.produceTV();
//        tv.play();
        
        // 將具體類名寫入配置文件中,再通過Java反射機制讀取XML格式文件,根據類名生成對象並返回
        factory = (TVFactory) XMLUtil.getBean();
        tv = factory.produceTV();
        tv.play();
    }

}

將config.xml文件中的“xxxFactory”更改為需要生成對象的類名“HaierTVFactory”,運行客戶端結果如下:

海爾電視播放中...

抽象工廠模式中例子的客戶端改進與此相同。

 

 

6大設計原則,與常見設計模式(概述):http://www.cnblogs.com/LangZXG/p/6204142.html

類圖基礎知識:http://www.cnblogs.com/LangZXG/p/6208716.html

 

注:轉載請注明出處   http://www.cnblogs.com/LangZXG/p/6249425.html

 


免責聲明!

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



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