1 場景問題#
1.1 選擇組裝電腦的配件##
舉個生活中常見的例子——組裝電腦,我們在組裝電腦的時候销凑,通常需要選擇一系列的配件丛晌,比如:CPU、硬盤斗幼、內(nèi)存澎蛛、主板、電源蜕窿、機箱等等瓶竭。為了使討論簡單點,只考慮選擇CPU和主板的問題渠羞。
事實上,我們在選擇CPU的時候智哀,面臨一系列的問題次询,比如:品牌、型號瓷叫、針腳數(shù)目屯吊、主頻等問題,只有把這些都確定下來摹菠,才能確定具體的CPU盒卸。同樣,在選擇主板的時候次氨,也有一系列的問題蔽介,比如:品牌、芯片組煮寡、集成芯片虹蓄、總線頻率等問題,也只有這些都確定了幸撕,才能確定具體的主板薇组。
選擇不同的CPU和主板,是每個客戶去組裝電腦的時候坐儿,向裝機公司提出的要求律胀,也就是我們每個人自己擬定的裝機方案宋光。
在最終確定這個裝機方案之前,還需要整體考慮各個配件之間的兼容性炭菌,比如:CPU和主板罪佳,如果CPU針腳數(shù)和主板提供的CPU插口不兼容,是無法組裝的娃兽。也就是說菇民,裝機方案是有整體性的,里面選擇的各個配件之間是有關(guān)聯(lián)的投储。
對于裝機工程師而言第练,他只知道組裝一臺電腦,需要相應(yīng)的配件玛荞,但是具體使用什么樣的配件娇掏,還得由客戶說了算。也就是說裝機工程師只是負責(zé)組裝勋眯,而客戶負責(zé)選擇裝配所需要的具體的配件婴梧。因此,當(dāng)裝機工程師為不同的客戶組裝電腦時客蹋,只需要按照客戶的裝機方案塞蹭,去獲取相應(yīng)的配件,然后組裝即可讶坯。
現(xiàn)在需要使用程序來把這個裝機的過程番电,尤其是選擇組裝電腦配件的過程實現(xiàn)出來,該如何實現(xiàn)呢辆琅?
1.2 不用模式的解決方案##
考慮客戶的功能漱办,需要選擇自己需要的CPU和主板,然后告訴裝機工程師自己的選擇婉烟,接下來就等著裝機工程師組裝機器了娩井。
對裝機工程師而言,只是知道CPU和主板的接口似袁,而不知道具體實現(xiàn)洞辣,很明顯可以用上簡單工廠或工廠方法模式,為了簡單叔营,這里選用簡單工廠吧屋彪。客戶告訴裝機工程師自己的選擇绒尊,然后裝機工程師會通過相應(yīng)的工廠去獲取相應(yīng)的實例對象畜挥。
- 先來看看CPU和主板的接口,先看CPU的接口定義婴谱,示例代碼如下:
/**
* CPU的接口
*/
public interface CPUApi {
/**
* 示意方法蟹但,CPU具有運算的功能
*/
public void calculate();
}
再看看主板的接口定義躯泰,示例代碼如下:
/**
* Mainboard的接口
*/
public interface MainboardApi {
public void installCPU();
}
- 接下來看看具體的CPU實現(xiàn),先看Intel的CPU實現(xiàn)华糖,示例代碼如下:
/**
* Intel的CPU實現(xiàn)
*/
public class IntelCPU implements CPUApi{
/**
* CPU的針腳數(shù)目
*/
private int pins = 0;
/**
* 構(gòu)造方法麦向,傳入CPU的針腳數(shù)目
* @param pins CPU的針腳數(shù)目
*/
public IntelCPU(int pins){
this.pins = pins;
}
public void calculate() {
System.out.println("now in Intel CPU,pins="+pins);
}
}
再看看AMD的CPU實現(xiàn),示例代碼如下:
/**
* AMD的CPU實現(xiàn)
*/
public class AMDCPU implements CPUApi{
/**
* CPU的針腳數(shù)目
*/
private int pins = 0;
/**
* 構(gòu)造方法客叉,傳入CPU的針腳數(shù)目
* @param pins CPU的針腳數(shù)目
*/
public AMDCPU(int pins){
this.pins = pins;
}
public void calculate() {
System.out.println("now in AMD CPU,pins="+pins);
}
}
- 接下來看看具體的主板實現(xiàn)诵竭,先看技嘉的主板實現(xiàn),示例代碼如下:
/**
* 技嘉的主板
*/
public class GAMainboard implements MainboardApi {
/**
* CPU插槽的孔數(shù)
*/
private int cpuHoles = 0;
/**
* 構(gòu)造方法兼搏,傳入CPU插槽的孔數(shù)
* @param cpuHoles CPU插槽的孔數(shù)
*/
public GAMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("now in GAMainboard,cpuHoles=" + cpuHoles);
}
}
再看看微星的主板實現(xiàn)卵慰,示例代碼如下:
/**
* 微星的主板
*/
public class MSIMainboard implements MainboardApi{
/**
* CPU插槽的孔數(shù)
*/
private int cpuHoles = 0;
/**
* 構(gòu)造方法,傳入CPU插槽的孔數(shù)
* @param cpuHoles CPU插槽的孔數(shù)
*/
public MSIMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("now in MSIMainboard,cpuHoles=" + cpuHoles);
}
}
- 接下來看看創(chuàng)建CPU和主板的工廠佛呻,先看創(chuàng)建CPU的工廠實現(xiàn)裳朋,示例代碼如下:
/**
* 創(chuàng)建CPU的簡單工廠
*/
public class CPUFactory {
/**
* 創(chuàng)建CPU接口對象的方法
* @param type 選擇CPU類型的參數(shù)
* @return CPU接口對象的方法
*/
public static CPUApi createCPUApi(int type){
CPUApi cpu = null;
//根據(jù)參數(shù)來選擇并創(chuàng)建相應(yīng)的CPU對象
if(type==1){
cpu = new IntelCPU(1156);
}else if(type==2){
cpu = new AMDCPU(939);
}
return cpu;
}
}
再看看創(chuàng)建主板的工廠實現(xiàn),示例代碼如下:
/**
* 創(chuàng)建主板的簡單工廠
*/
public class MainboardFactory {
/**
* 創(chuàng)建主板接口對象的方法
* @param type 選擇主板類型的參數(shù)
* @return 主板接口對象的方法
*/
public static MainboardApi createMainboardApi(int type){
MainboardApi mainboard = null;
//根據(jù)參數(shù)來選擇并創(chuàng)建相應(yīng)的主板對象
if(type==1){
mainboard = new GAMainboard(1156);
}else if(type==2){
mainboard = new MSIMainboard(939);
}
return mainboard;
}
}
- 接下來看看裝機工程師的實現(xiàn)吓著,示例代碼如下:
/**
* 裝機工程師的類
*/
public class ComputerEngineer {
/**
* 定義組裝機器需要的CPU
*/
private CPUApi cpu= null;
/**
* 定義組裝機器需要的主板
*/
private MainboardApi mainboard = null;
/**
* 裝機過程
* @param cpuType 客戶選擇所需CPU的類型
* @param mainboardType 客戶選擇所需主板的類型
*/
public void makeComputer(int cpuType,int mainboardType){
//1:首先準備好裝機所需要的配件
prepareHardwares(cpuType,mainboardType);
//2:組裝機器
//3:測試機器
//4:交付客戶
}
/**
* 準備裝機所需要的配件
* @param cpuType 客戶選擇所需CPU的類型
* @param mainboardType 客戶選擇所需主板的類型
*/
private void prepareHardwares(int cpuType,int mainboardType){
//這里要去準備CPU和主板的具體實現(xiàn)鲤嫡,為了示例簡單,這里只準備這兩個
//可是绑莺,裝機工程師并不知道如何去創(chuàng)建暖眼,怎么辦呢?
//直接找相應(yīng)的工廠獲取
this.cpu = CPUFactory.createCPUApi(cpuType);
this.mainboard = MainboardFactory.createMainboardApi(mainboardType);
//測試一下配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
- 看看此時的客戶端纺裁,應(yīng)該通過裝機工程師來組裝電腦罢荡,客戶需要告訴裝機工程師他選擇的配件,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建裝機工程師對象
ComputerEngineer engineer = new ComputerEngineer();
//告訴裝機工程師自己選擇的配件对扶,讓裝機工程師組裝電腦
engineer.makeComputer(1,1);
}
}
1.3 有何問題##
看了上面的實現(xiàn),會感覺到很簡單嘛惭缰,通過使用簡單工廠來獲取需要的CPU和主板對象浪南,然后就可以組裝電腦了。有何問題呢漱受?
雖然上面的實現(xiàn)络凿,通過簡單工廠解決解決了:對于裝機工程師,只知CPU和主板的接口昂羡,而不知道具體實現(xiàn)的問題絮记。但還有一個問題沒有解決,什么問題呢虐先?那就是這些CPU對象和主板對象其實是有關(guān)系的怨愤,是需要相互匹配的。而在上面的實現(xiàn)中蛹批,并沒有維護這種關(guān)聯(lián)關(guān)系撰洗,CPU和主板是由客戶隨意選擇的篮愉。這是有問題的。
這就是沒有維護配件之間的關(guān)系造成的差导。該怎么解決這個問題呢试躏?
2 解決方案#
2.1 抽象工廠模式來解決##
用來解決上述問題的一個合理的解決方案就是抽象工廠模式。那么什么是抽象工廠模式呢设褐?
- 抽象工廠模式定義
- 應(yīng)用抽象工廠模式來解決的思路
仔細分析上面的問題颠蕴,其實有兩個問題點,一個是只知道所需要的一系列對象的接口助析,而不知具體實現(xiàn)犀被,或者是不知道具體使用哪一個實現(xiàn);另外一個是這一系列對象是相關(guān)或者相互依賴的貌笨。也就是說既要創(chuàng)建接口的對象弱判,還要約束它們之間的關(guān)系。
有朋友可能會想锥惋,工廠方法模式或者是簡單工廠昌腰,不就可以解決只知接口而不知實現(xiàn)的問題嗎?怎么這些問題又冒出來了呢膀跌?
請注意遭商,這里要解決的問題和工廠方法模式或簡單工廠解決的問題是有很大不同的,工廠方法模式或簡單工廠關(guān)注的是單個產(chǎn)品對象的創(chuàng)建捅伤,比如創(chuàng)建CPU的工廠方法劫流,它就只關(guān)心如何創(chuàng)建CPU的對象,而創(chuàng)建主板的工廠方法丛忆,就只關(guān)心如何創(chuàng)建主板對象祠汇。
這里要解決的問題是,要創(chuàng)建一系列的產(chǎn)品對象熄诡,而且這一系列對象是構(gòu)建新的對象所需要的組成部分可很,也就是這一系列被創(chuàng)建的對象相互之間是有約束的。
解決這個問題的一個解決方案就是抽象工廠模式凰浮。在這個模式里面我抠,會定義一個抽象工廠,在里面虛擬的創(chuàng)建客戶端需要的這一系列對象袜茧,所謂虛擬的就是定義創(chuàng)建這些對象的抽象方法菜拓,并不去真的實現(xiàn),然后由具體的抽象工廠的子類來提供這一系列對象的創(chuàng)建笛厦。這樣一來可以為同一個抽象工廠提供很多不同的實現(xiàn)纳鼎,那么創(chuàng)建的這一系列對象也就不一樣了,也就是說裳凸,抽象工廠在這里起到一個約束的作用喷橙,并提供所有子類的一個統(tǒng)一外觀啥么,來讓客戶端使用。
2.2 模式結(jié)構(gòu)和說明##
抽象工廠模式結(jié)構(gòu)如圖所示:
AbstractFactory:抽象工廠贰逾,定義創(chuàng)建一系列產(chǎn)品對象的操作接口悬荣。
ConcreteFactory:具體的工廠,實現(xiàn)抽象工廠定義的方法疙剑,具體實現(xiàn)一系列產(chǎn)品對象的創(chuàng)建氯迂。
AbstractProduct:定義一類產(chǎn)品對象的接口。
ConcreteProduct:具體的產(chǎn)品實現(xiàn)對象言缤,通常在具體工廠里面嚼蚀,會選擇具體的產(chǎn)品實現(xiàn)對象,來創(chuàng)建符合抽象工廠定義的方法返回的產(chǎn)品類型的對象管挟。
Client:客戶端轿曙,主要使用抽象工廠來獲取一系列所需要的產(chǎn)品對象,然后面向這些產(chǎn)品對象的接口編程僻孝,以實現(xiàn)需要的功能导帝。
2.3 抽象工廠模式示例代碼##
- 先看看抽象工廠的定義,示例代碼如下:
/**
* 抽象工廠的接口穿铆,聲明創(chuàng)建抽象產(chǎn)品對象的操作
*/
public interface AbstractFactory {
/**
* 示例方法您单,創(chuàng)建抽象產(chǎn)品A的對象
* @return 抽象產(chǎn)品A的對象
*/
public AbstractProductA createProductA();
/**
* 示例方法,創(chuàng)建抽象產(chǎn)品B的對象
* @return 抽象產(chǎn)品B的對象
*/
public AbstractProductB createProductB();
}
- 接下來看看產(chǎn)品的定義荞雏,由于只是示意虐秦,并沒有去定義具體的方法,示例代碼如下:
/**
* 抽象產(chǎn)品A的接口
*/
public interface AbstractProductA {
//定義抽象產(chǎn)品A相關(guān)的操作
}
/**
* 抽象產(chǎn)品B的接口
*/
public interface AbstractProductB {
//定義抽象產(chǎn)品B相關(guān)的操作
}
- 同樣的凤优,產(chǎn)品的各個實現(xiàn)對象也是空的悦陋,示例代碼如下:
/**
* 產(chǎn)品A的具體實現(xiàn)
*/
public class ProductA1 implements AbstractProductA {
//實現(xiàn)產(chǎn)品A的接口中定義的操作
}
/**
* 產(chǎn)品A的具體實現(xiàn)
*/
public class ProductA2 implements AbstractProductA {
//實現(xiàn)產(chǎn)品A的接口中定義的操作
}
/**
* 產(chǎn)品B的具體實現(xiàn)
*/
public class ProductB1 implements AbstractProductB {
//實現(xiàn)產(chǎn)品B的接口中定義的操作
}
/**
* 產(chǎn)品B的具體實現(xiàn)
*/
public class ProductB2 implements AbstractProductB {
//實現(xiàn)產(chǎn)品B的接口中定義的操作
}
- 接下來看看具體的工廠的實現(xiàn)示意,示例代碼如下:
/**
* 具體的工廠實現(xiàn)對象筑辨,實現(xiàn)創(chuàng)建具體的產(chǎn)品對象的操作
*/
public class ConcreteFactory1 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ProductA1();
}
public AbstractProductB createProductB() {
return new ProductB1();
}
}
/**
* 具體的工廠實現(xiàn)對象叨恨,實現(xiàn)創(chuàng)建具體的產(chǎn)品對象的操作
*/
public class ConcreteFactory2 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ProductA2();
}
public AbstractProductB createProductB() {
return new ProductB2();
}
}
- 最后來看看客戶端的實現(xiàn)示意,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建抽象工廠對象
AbstractFactory af = new ConcreteFactory1();
//通過抽象工廠來獲取一系列的對象挖垛,如產(chǎn)品A和產(chǎn)品B
af.createProductA();
af.createProductB();
}
}
2.4 使用抽象工廠模式重寫示例##
要使用抽象工廠模式來重寫示例,先來看看如何使用抽象工廠模式來解決前面提出的問題秉颗。
裝機工程師要組裝電腦對象痢毒,需要一系列的產(chǎn)品對象,比如CPU蚕甥、主板等哪替,于是創(chuàng)建一個抽象工廠給裝機工程師使用,在這個抽象工廠里面定義抽象的創(chuàng)建CPU和主板的方法菇怀,這個抽象工廠就相當(dāng)于一個抽象的裝機方案凭舶,在這個裝機方案里面晌块,各個配件是能夠相互匹配的。
每個裝機的客戶帅霜,會提出他們自己的具體裝機方案匆背,或者是選擇已有的裝機方案,相當(dāng)于為抽象工廠提供了具體的子類身冀,在這些具體的裝機方案類里面钝尸,會創(chuàng)建具體的CPU和主板實現(xiàn)對象。
此時系統(tǒng)的結(jié)構(gòu)如圖所示:
雖然說是重寫示例搂根,但并不是前面寫的都不要了珍促,而是修改前面的示例,使它能更好的實現(xiàn)需要的功能剩愧。
前面示例實現(xiàn)的CPU接口和CPU實現(xiàn)對象猪叙,還有主板的接口和實現(xiàn)對象,都不需要變化仁卷,這里就不去贅述了穴翩。
前面示例中的創(chuàng)建CPU的簡單工廠和創(chuàng)建主板的簡單工廠,都不再需要了五督,直接刪除即可藏否,這里也就不去管了。
看看新加入的抽象工廠的定義充包,示例代碼如下:
/**
* 抽象工廠的接口副签,聲明創(chuàng)建抽象產(chǎn)品對象的操作
*/
public interface AbstractFactory {
/**
* 創(chuàng)建CPU的對象
* @return CPU的對象
*/
public CPUApi createCPUApi();
/**
* 創(chuàng)建主板的對象
* @return 主板的對象
*/
public MainboardApi createMainboardApi();
}
- 再看看抽象工廠的實現(xiàn)對象,也就是具體的裝機方案對象基矮,先看看裝機方案一的實現(xiàn)淆储,示例代碼如下:
/**
* 裝機方案一:Intel 的CPU + 技嘉的主板
* 這里創(chuàng)建CPU和主板對象的時候,是對應(yīng)的家浇,能匹配上的
*/
public class Schema1 implements AbstractFactory{
public CPUApi createCPUApi() {
return new IntelCPU(1156);
}
public MainboardApi createMainboardApi() {
return new GAMainboard(1156);
}
}
/**
* 裝機方案二:AMD的CPU + 微星的主板
* 這里創(chuàng)建CPU和主板對象的時候本砰,是對應(yīng)的,能匹配上的
*/
public class Schema2 implements AbstractFactory{
public CPUApi createCPUApi() {
return new AMDCPU(939);
}
public MainboardApi createMainboardApi() {
return new MSIMainboard(939);
}
}
- 再來看看裝機工程師類的實現(xiàn)钢悲,在現(xiàn)在的實現(xiàn)里面点额,裝機工程師相當(dāng)于使用抽象工廠的客戶端,雖然是由真正的客戶來選擇和創(chuàng)建具體的工廠對象莺琳,但是使用抽象工廠的是裝機工程師對象还棱。
裝機工程師類跟前面的實現(xiàn)相比,主要的變化是:從客戶端惭等,不再傳入選擇CPU和主板的參數(shù)珍手,而是直接傳入客戶選擇并創(chuàng)建好的裝機方案對象。這樣就避免了單獨去選擇CPU和主板,客戶要選就是一套琳要,就是一個系列寡具。示例代碼如下:
/**
* 裝機工程師的類
*/
public class ComputerEngineer {
/**
* 定義組裝機器需要的CPU
*/
private CPUApi cpu= null;
/**
* 定義組裝機器需要的主板
*/
private MainboardApi mainboard = null;
/**
* 裝機過程
* @param schema 客戶選擇的裝機方案
*/
public void makeComputer(AbstractFactory schema){
//1:首先準備好裝機所需要的配件
prepareHardwares(schema);
//2:組裝機器
//3:測試機器
//4:交付客戶
}
/**
* 準備裝機所需要的配件
* @param schema 客戶選擇的裝機方案
*/
private void prepareHardwares(AbstractFactory schema){
//這里要去準備CPU和主板的具體實現(xiàn),為了示例簡單稚补,這里只準備這兩個
//可是童叠,裝機工程師并不知道如何去創(chuàng)建,怎么辦呢孔厉?
//使用抽象工廠來獲取相應(yīng)的接口對象
this.cpu = schema.createCPUApi();
this.mainboard = schema.createMainboardApi();
//測試一下配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
- 都定義好了拯钻,看看客戶端如何使用抽象工廠,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建裝機工程師對象
ComputerEngineer engineer = new ComputerEngineer();
//客戶選擇并創(chuàng)建需要使用的裝機方案對象
AbstractFactory schema = new Schema1();
//告訴裝機工程師自己選擇的裝機方案撰豺,讓裝機工程師組裝電腦
engineer.makeComputer(schema);
}
}
如同前面的示例粪般,定義了一個抽象工廠AbstractFactory,在里面定義了創(chuàng)建CPU和主板對象的接口的方法污桦,但是在抽象工廠里面亩歹,并沒有指定具體的CPU和主板的實現(xiàn),也就是無須指定它們具體的實現(xiàn)類凡橱。
CPU和主板是相關(guān)的對象小作,是構(gòu)建電腦的一系列相關(guān)配件,這個抽象工廠就相當(dāng)于一個裝機方案稼钩,客戶選擇裝機方案的時候顾稀,一選就是一套,CPU和主板是確定好的坝撑,不讓客戶分開選擇静秆,這就避免了出現(xiàn)不匹配的錯誤。
3 模式講解#
3.1 認識抽象工廠模式##
- 模式的功能
抽象工廠的功能是為一系列相關(guān)對象或相互依賴的對象創(chuàng)建一個接口巡李,一定要注意抚笔,這個接口內(nèi)的方法不是任意堆砌的鹊碍,而是一系列相關(guān)或相互依賴的方法声诸,比如上面例子中的CPU和主板,都是為了組裝一臺電腦的相關(guān)對象俄精。
從某種意義上看狱从,抽象工廠其實是一個產(chǎn)品系列膨蛮,或者是產(chǎn)品簇。上面例子中的抽象工廠就可以看成是電腦簇季研,每個不同的裝機方案敞葛,代表一種具體的電腦系列。
- 實現(xiàn)成接口
AbstractFactory在Java中通常實現(xiàn)成為接口训貌,大家不要被名稱誤導(dǎo)了,以為是實現(xiàn)成為抽象類,當(dāng)然递沪,如果需要為這個產(chǎn)品簇提供公共的功能豺鼻,也不是不可以把AbstractFactory實現(xiàn)成為抽象類,但一般不這么做款慨。
- 使用工廠方法
AbstractFactory定義了創(chuàng)建產(chǎn)品所需要的接口儒飒,具體的實現(xiàn)是在實現(xiàn)類里面,通常在實現(xiàn)類里面就需要選擇多種更具體的實現(xiàn)檩奠,所以AbstractFactory定義的創(chuàng)建產(chǎn)品的方法可以看成是工廠方法桩了,而這些工廠方法的具體實現(xiàn)就延遲到了具體的工廠里面。也就是說使用工廠方法來實現(xiàn)抽象工廠埠戳。
- 切換產(chǎn)品簇
由于抽象工廠定義的一系列對象井誉,通常是相關(guān)或者相依賴的,這些產(chǎn)品對象就構(gòu)成了一個產(chǎn)品簇整胃,也就是抽象工廠定義了一個產(chǎn)品簇颗圣。這就帶來非常大的靈活性,切換一個產(chǎn)品簇的時候屁使,只要提供不同的抽象工廠實現(xiàn)就好了在岂,也就是說現(xiàn)在是以產(chǎn)品簇做為一個整體被切換。
- 抽象工廠模式的調(diào)用順序示意圖
3.2 定義可擴展的工廠##
在前面的示例中蛮寂,抽象工廠為每一種它能創(chuàng)建的產(chǎn)品對象都定義了相應(yīng)的方法蔽午,比如創(chuàng)建CPU的方法和創(chuàng)建主板的方法等。
這種實現(xiàn)有一個麻煩酬蹋,就是如果在產(chǎn)品簇中要新增加一種產(chǎn)品及老,比如現(xiàn)在要求抽象工廠除了能夠創(chuàng)建CPU和主板外,還要能夠創(chuàng)建內(nèi)存對象除嘹,那么就需要在抽象工廠里面添加創(chuàng)建內(nèi)存的這么一個方法写半。當(dāng)抽象工廠一發(fā)生變化,所有的具體工廠實現(xiàn)都要發(fā)生變化尉咕,這非常的不靈活叠蝇。
現(xiàn)在有一種相對靈活,但是不太安全的改進方式來解決這個問題年缎,思路如下:抽象工廠里面不需要定義那么多方法悔捶,定義一個方法就可以了,給這個方法設(shè)置一個參數(shù)单芜,通過這個參數(shù)來判斷具體創(chuàng)建什么產(chǎn)品對象蜕该;由于只有一個方法,在返回類型上就不能是具體的某個產(chǎn)品類型了洲鸠,只能是所有的產(chǎn)品對象都繼承或者實現(xiàn)的這么一個類型堂淡,比如讓所有的產(chǎn)品都實現(xiàn)某個接口馋缅,或者干脆使用Object類型。
還是看看代碼來體會一下绢淀,把前面那個示例改造成可擴展的工廠實現(xiàn)萤悴。
- 先來改造抽象工廠,示例代碼如下:
/**
* 可擴展的抽象工廠的接口
*/
public interface AbstractFactory {
/**
* 一個通用的創(chuàng)建產(chǎn)品對象的方法皆的,為了簡單覆履,直接返回Object
* 也可以為所有被創(chuàng)建的產(chǎn)品定義一個公共的接口
* @param type 具體創(chuàng)建的產(chǎn)品類型標識
* @return 創(chuàng)建出的產(chǎn)品對象
*/
public Object createProduct(int type);
}
這里要特別注意傳入createProduct的參數(shù)所代表的含義,這個參數(shù)只是用來標識現(xiàn)在是在創(chuàng)建什么類型的產(chǎn)品费薄,比如標識現(xiàn)在是創(chuàng)建CPU還是創(chuàng)建主板硝全,一般這個type的含義到此就結(jié)束了,不再進一步表示具體是什么樣的CPU或具體什么樣的主板楞抡,也就是說type不再表示具體是創(chuàng)建Intel的CPU還是創(chuàng)建AMD的CPU伟众,這就是一個參數(shù)所代表的含義的深度的問題,要注意拌倍。雖然也可以延伸參數(shù)的含義到具體的實現(xiàn)上赂鲤,但這不是可擴展工廠這種設(shè)計方式的本意,一般也不這么做柱恤。
CPU的接口和實現(xiàn)数初,主板的接口和實現(xiàn)跟前面的示例是一樣的,就不再示范了梗顺。CPU還是分成Intel的CPU和AMD的CPU泡孩,主板還是分成技嘉的主板和微星的主板。
下面來提供具體的工廠實現(xiàn)寺谤,也就是相當(dāng)于以前的裝機方案仑鸥,先改造原來的方案一吧,現(xiàn)在的實現(xiàn)會有較大的變化变屁,示例代碼如下:
/**
* 裝機方案一:Intel 的CPU + 技嘉的主板
* 這里創(chuàng)建CPU和主板對象的時候眼俊,是對應(yīng)的,能匹配上的
*/
public class Schema1 implements AbstractFactory{
public Object createProduct(int type) {
Object retObj = null;
//type為1表示創(chuàng)建CPU粟关,type為2表示創(chuàng)建主板
if(type==1){
retObj = new IntelCPU(1156);
}else if(type==2){
retObj = new GAMainboard(1156);
}
return retObj;
}
}
/**
* 裝機方案二:AMD的CPU + 微星的主板
* 這里創(chuàng)建CPU和主板對象的時候疮胖,是對應(yīng)的,能匹配上的
*/
public class Schema2 implements AbstractFactory{
public Object createProduct(int type) {
Object retObj = null;
//type為1表示創(chuàng)建CPU闷板,type為2表示創(chuàng)建主板
if(type==1){
retObj = new AMDCPU(939);
}else if(type==2){
retObj = new MSIMainboard(939);
}
return retObj;
}
}
- 看看這個時候使用抽象工廠的客戶端實現(xiàn)澎灸,也就是在裝機工程師類里面,通過抽象工廠來獲取相應(yīng)的配件產(chǎn)品對象遮晚,示例代碼如下:
public class ComputerEngineer {
private CPUApi cpu= null;
private MainboardApi mainboard = null;
public void makeComputer(AbstractFactory schema){
prepareHardwares(schema);
}
private void prepareHardwares(AbstractFactory schema){
//這里要去準備CPU和主板的具體實現(xiàn)性昭,為了示例簡單,這里只準備這兩個
//可是县遣,裝機工程師并不知道如何去創(chuàng)建糜颠,怎么辦呢汹族?
//使用抽象工廠來獲取相應(yīng)的接口對象
this.cpu = (CPUApi)schema.createProduct(1);
this.mainboard = (MainboardApi)schema.createProduct(2);
//測試一下配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
通過上面的示例,能看到可擴展工廠的基本實現(xiàn)其兴。從客戶端的代碼會發(fā)現(xiàn)鞠抑,為什么說這種方式是不太安全的呢?
你會發(fā)現(xiàn)創(chuàng)建產(chǎn)品對象返回來過后忌警,需要造型成為具體的對象,因為返回的是Object秒梳,如果這個時候沒有匹配上法绵,比如返回的不是CPU對象,但是要強制造型成為CPU酪碘,那么就會發(fā)生錯誤朋譬,因此這種實現(xiàn)方式的一個潛在缺點就是不太安全。
- 接下來兴垦,體會一下這種方式的靈活性:
假如現(xiàn)在要加入一個新的產(chǎn)品——內(nèi)存徙赢,當(dāng)然可以提供一個新的裝機方案來使用它,這樣已有的代碼就不需要變化了探越。
先看看內(nèi)存的接口吧狡赐,示例代碼如下:
/**
* 內(nèi)存的接口
*/
public interface MemoryApi {
/**
* 示意方法,內(nèi)存具有緩存數(shù)據(jù)的能力
*/
public void cacheData();
}
提供一個現(xiàn)代內(nèi)存的基本實現(xiàn)钦幔,示例代碼如下:
/**
* 現(xiàn)代內(nèi)存的類
*/
public class HyMemory implements MemoryApi{
public void cacheData() {
System.out.println("現(xiàn)在正在使用現(xiàn)代內(nèi)存");
}
}
現(xiàn)在想要使用這個新加入的產(chǎn)品枕屉,以前實現(xiàn)的代碼都不用變化,只需新添加一個方案鲤氢,在這個方案里面使用新的產(chǎn)品搀擂,然后客戶端使用這個新的方案即可,示例代碼如下:
/**
* 裝機方案三:Intel 的CPU + 技嘉的主板 + 現(xiàn)代的內(nèi)存
*/
public class Scheme3 implements AbstractFactory{
public Object createProduct(int type) {
Object retObj = null;
//type為1表示創(chuàng)建CPU卷玉,type為2表示創(chuàng)建主板哨颂,type為3表示創(chuàng)建內(nèi)存
if(type==1){
retObj = new IntelCPU(1156);
}else if(type==2){
retObj = new GAMainboard(1156);
}
//創(chuàng)建新添加的產(chǎn)品
else if(type==3){
retObj = new HyMemory();
}
return retObj;
}
}
這個時候的裝機工程師類,如果要創(chuàng)建帶內(nèi)存的機器相种,需要在裝機工程師類里面添加對內(nèi)存的使用威恼,示例代碼如下:
public class ComputerEngineer {
private CPUApi cpu= null;
private MainboardApi mainboard = null;
/**
* 定義組裝機器需要的內(nèi)存
*/
private MemoryApi memory = null;
public void makeComputer(AbstractFactory schema){
prepareHardwares(schema);
}
private void prepareHardwares(AbstractFactory schema){
//使用抽象工廠來獲取相應(yīng)的接口對象
this.cpu = (CPUApi)schema.createProduct(1);
this.mainboard = (MainboardApi)schema.createProduct(2);
this.memory = (MemoryApi)schema.createProduct(3);
//測試一下配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
if(memory!=null){
this.memory.cacheData();
}
}
}
3.3 抽象工廠模式和DAO##
- 首先來看看什么是DAO
DAO:數(shù)據(jù)訪問對象,是Data Access Object首字母的簡寫蚂子。
DAO是JEE(也稱JavaEE沃测,原J2EE)中的一個標準模式,通過它來解決訪問數(shù)據(jù)對象所面臨的一系列問題食茎,比如:數(shù)據(jù)源不同蒂破、存儲類型不同、訪問方式不同别渔、供應(yīng)商不同附迷、版本不同等等惧互,這些不同會造成訪問數(shù)據(jù)的實現(xiàn)上差別很大。
數(shù)據(jù)源的不同喇伯,比如存放于數(shù)據(jù)庫的數(shù)據(jù)源喊儡,存放于LDAP(輕型目錄訪問協(xié)議)的數(shù)據(jù)源;又比如存放于本地的數(shù)據(jù)源和遠程服務(wù)器上的數(shù)據(jù)源等等
存儲類型的不同稻据,比如關(guān)系型數(shù)據(jù)庫(RDBMS)艾猜、面向?qū)ο髷?shù)據(jù)庫(ODBMS)、純文件捻悯、XML等等
訪問方式的不同匆赃,比如訪問關(guān)系型數(shù)據(jù)庫,可以用JDBC今缚、EntityBean算柳、JPA等來實現(xiàn),當(dāng)然也可以采用一些流行的框架姓言,如Hibernate瞬项、IBatis等等
供應(yīng)商的不同,比如關(guān)系型數(shù)據(jù)庫何荚,流行如Oracel囱淋、DB2、SqlServer餐塘、MySql等等绎橘,它們的供應(yīng)商是不同的
版本不同,比如關(guān)系型數(shù)據(jù)庫唠倦,不同的版本称鳞,實現(xiàn)的功能是有差異的,就算是對標準的SQL的支持稠鼻,也是有差異的
但是對于需要進行數(shù)據(jù)訪問的邏輯層而言冈止,它可不想面對這么多不同,也不想處理這么多差異候齿,它希望能以一個統(tǒng)一的方式來訪問數(shù)據(jù)熙暴。此時系統(tǒng)結(jié)構(gòu)如圖所示:
也就是說,DAO需要抽象和封裝所有對數(shù)據(jù)的訪問慌盯,DAO承擔(dān)和數(shù)據(jù)倉庫交互的職責(zé)周霉,這也意味著,訪問數(shù)據(jù)所面臨的所有問題亚皂,都需要DAO在內(nèi)部來自行解決俱箱。
- DAO和抽象工廠的關(guān)系
事實上,在實現(xiàn)DAO模式的時候灭必,最常見的實現(xiàn)策略就是使用工廠的策略狞谱,而且多是通過抽象工廠模式來實現(xiàn)乃摹,當(dāng)然在使用抽象工廠模式來實現(xiàn)的時候,可以結(jié)合工廠方法模式跟衅。因此DAO模式和抽象工廠模式有很大的聯(lián)系孵睬。
- DAO模式的工廠實現(xiàn)策略
(1)采用工廠方法模式
假如現(xiàn)在在一個訂單處理的模塊里面,大家都知道伶跷,訂單通常又分成兩個部分掰读,一個部分是訂單主記錄或者是訂單主表,另一個部分是訂單明細記錄或者是訂單子表叭莫,那么現(xiàn)在業(yè)務(wù)對象需要操作訂單的主記錄磷支,也需要操作訂單的子記錄。
如果這個時候的業(yè)務(wù)比較簡單食寡,而且對數(shù)據(jù)的操作是固定的,比如就是操作數(shù)據(jù)庫廓潜,不管訂單的業(yè)務(wù)如何變化抵皱,底層數(shù)據(jù)存儲都是一樣的,那么這種情況下辩蛋,可以采用工廠方法模式呻畸,此時系統(tǒng)結(jié)構(gòu)如圖所示:
從上面的結(jié)構(gòu)示意圖可以看出,如果底層存儲固定的時候悼院,DAOFactory就相當(dāng)于工廠方法模式中的Creator伤为,在里面定義兩個工廠方法,分別創(chuàng)建訂單主記錄的DAO對象和創(chuàng)建訂單子記錄的DAO對象据途,因為固定是數(shù)據(jù)庫實現(xiàn)绞愚,因此提供一個具體的工廠RdbDAOFactory(Rdb:關(guān)系型數(shù)據(jù)庫),來實現(xiàn)對象的創(chuàng)建颖医。也就是說DAO可以采用工廠方法模式來實現(xiàn)位衩。
采用工廠方法模式的情況,要求DAO底層存儲實現(xiàn)方式是固定的熔萧,這種多用在一些簡單的小項目開發(fā)上糖驴。
(2)采用抽象工廠模式
實際上更多的時候,DAO底層存儲實現(xiàn)方式是不固定的佛致,DAO通常會支持多種存儲實現(xiàn)方式贮缕,具體使用哪一種存儲方式可能是由應(yīng)用動態(tài)決定,或者是通過配置來指定俺榆。這種情況多見于產(chǎn)品開發(fā)感昼、或者是稍復(fù)雜的應(yīng)用、或者是較大的項目中罐脊。
對于底層存儲方式不固定的時候抑诸,一般是采用抽象工廠模式來實現(xiàn)DAO烂琴。比如現(xiàn)在的實現(xiàn)除了RDB的實現(xiàn),還會有Xml的實現(xiàn)蜕乡,它們會被應(yīng)用動態(tài)的選擇奸绷,此時系統(tǒng)結(jié)構(gòu)如圖所示:
從上面的結(jié)構(gòu)示意圖可以看出,采用抽象工廠模式來實現(xiàn)DAO的時候层玲,DAOFactory就相當(dāng)于抽象工廠号醉,里面定義一系列創(chuàng)建相關(guān)對象的方法,分別是創(chuàng)建訂單主記錄的DAO對象和創(chuàng)建訂單子記錄的DAO對象辛块,此時OrderMainDAO和OrderDetailDAO就相當(dāng)于被創(chuàng)建的產(chǎn)品畔派,RdbDAOFactory和XmlDAOFactory就相當(dāng)于抽象工廠的具體實現(xiàn),在它們里面會選擇相應(yīng)的具體的產(chǎn)品實現(xiàn)來創(chuàng)建對象润绵。
- 代碼示例使用抽象工廠實現(xiàn)DAO模式
(1)先看看抽象工廠的代碼實現(xiàn)线椰,示例代碼如下:
/**
* 抽象工廠,創(chuàng)建訂單主尘盼、子記錄對應(yīng)的DAO對象
*/
public abstract class DAOFactory {
/**
* 創(chuàng)建訂單主記錄對應(yīng)的DAO對象
* @return 訂單主記錄對應(yīng)的DAO對象
*/
public abstract OrderMainDAO createOrderMainDAO();
/**
* 創(chuàng)建訂單子記錄對應(yīng)的DAO對象
* @return 訂單子記錄對應(yīng)的DAO對象
*/
public abstract OrderDetailDAO createOrderDetailDAO();
}
(2)看看產(chǎn)品對象的接口憨愉,就是訂單主、子記錄的DAO定義卿捎,先看訂單主記錄的DAO定義配紫,示例代碼如下:
/**
* 訂單主記錄對應(yīng)的DAO操作接口
*/
public interface OrderMainDAO {
/**
* 示意方法,保存訂單主記錄
*/
public void saveOrderMain();
}
再看看訂單子記錄的DAO定義午阵,示例代碼如下:
/**
* 訂單子記錄對應(yīng)的DAO操作接口
*/
public interface OrderDetailDAO {
/**
* 示意方法躺孝,保存訂單子記錄
*/
public void saveOrderDetail();
}
(3)接下來實現(xiàn)訂單主、子記錄的DAO底桂,先看關(guān)系型數(shù)據(jù)庫的實現(xiàn)方式植袍,示例代碼如下:
public class RdbMainDAOImpl implements OrderMainDAO{
public void saveOrderMain() {
System.out.println("now in RdbMainDAOImpl saveOrderMain");
}
}
public class RdbDetailDAOImpl implements OrderDetailDAO{
public void saveOrderDetail() {
System.out.println("now in RdbDetailDAOImpl saveOrderDetail");
}
}
Xml實現(xiàn)的方式一樣,為了演示簡單籽懦,都是輸出了一句話奋单,示例代碼如下:
public class XmlMainDAOImpl implements OrderMainDAO{
public void saveOrderMain() {
System.out.println("now in XmlMainDAOImpl saveOrderMain");
}
}
(4)再看看具體的工廠實現(xiàn),先看關(guān)系型數(shù)據(jù)庫實現(xiàn)方式的工廠猫十,示例代碼如下:
public class RdbDAOFactory extends DAOFactory{
public OrderDetailDAO createOrderDetailDAO() {
return new RdbDetailDAOImpl();
}
public OrderMainDAO createOrderMainDAO() {
return new RdbMainDAOImpl();
}
}
Xml實現(xiàn)方式的工廠览濒,示例代碼如下:
public class XmlDAOFactory extends DAOFactory {
public OrderDetailDAO createOrderDetailDAO() {
return new XmlDetailDAOImpl();
}
public OrderMainDAO createOrderMainDAO() {
return new XmlMainDAOImpl();
}
}
(5)好了,使用抽象工廠來簡單的實現(xiàn)了DAO模式拖云,那么在客戶端贷笛,通常是由業(yè)務(wù)對象來調(diào)用DAO,那么該怎么使用這個DAO呢宙项?示例代碼如下:
public class BusinessObject {
public static void main(String[] args) {
//創(chuàng)建DAO的抽象工廠
DAOFactory df = new RdbDAOFactory();
//通過抽象工廠來獲取需要的DAO接口
OrderMainDAO mainDAO = df.createOrderMainDAO();
OrderDetailDAO detailDAO = df.createOrderDetailDAO();
//調(diào)用DAO來完成數(shù)據(jù)存儲的功能
mainDAO.saveOrderMain();
detailDAO.saveOrderDetail();
}
}
通過上面的示例乏苦,可以看出DAO可以采用抽象工廠模式來實現(xiàn),這也是大部分DAO實現(xiàn)采用的方式。
3.4 模式的優(yōu)缺點##
- 分離接口和實現(xiàn)
客戶端使用抽象工廠來創(chuàng)建需要的對象汇荐,而客戶端根本就不知道具體的實現(xiàn)是誰洞就,客戶端只是面向產(chǎn)品的接口編程而已,也就是說掀淘,客戶端從具體的產(chǎn)品實現(xiàn)中解耦旬蟋。
- 使得切換產(chǎn)品簇變得容易
因為一個具體的工廠實現(xiàn)代表的是一個產(chǎn)品簇,比如上面例子的Scheme1代表裝機方案一:Intel 的CPU + 技嘉的主板革娄,如果要切換成為Scheme2倾贰,那就變成了裝機方案二:AMD的CPU + 微星的主板。
客戶端選用不同的工廠實現(xiàn)拦惋,就相當(dāng)于是在切換不同的產(chǎn)品簇匆浙。
- 不太容易擴展新的產(chǎn)品
前面也提到這個問題了,如果需要給整個產(chǎn)品簇添加一個新的產(chǎn)品厕妖,那么就需要修改抽象工廠首尼,這樣就會導(dǎo)致修改所有的工廠實現(xiàn)類。在前面提供了一個可以擴展工廠的方式來解決這個問題言秸,但是又不夠安全软能。如何選擇,根據(jù)實際應(yīng)用來權(quán)衡吧井仰。
- 容易造成類層次復(fù)雜
在使用抽象工廠模式的時候,如果需要選擇的層次過多破加,那么會造成整個類層次變得復(fù)雜俱恶。
舉個例子來說,就比如前面講到的那個DAO的示例范舀,現(xiàn)在這個DAO只有一個選擇的層次合是,也就是選擇一下是使用關(guān)系型數(shù)據(jù)庫來實現(xiàn),還是用Xml來實現(xiàn)《Щ罚現(xiàn)在考慮這樣一種情況聪全,如果關(guān)系型數(shù)據(jù)庫實現(xiàn)里面又分成幾種,比如:基于Oracle的實現(xiàn)辅辩,基于SqlServer的實現(xiàn)难礼,基于MySql的實現(xiàn)等等。
那么客戶端怎么選呢玫锋?不會把所有可能的實現(xiàn)情況全部都做到一個層次上吧蛾茉,這個時候客戶端就需要一層一層選擇,也就是整個抽象工廠的實現(xiàn)也需要分出層次來撩鹿,每一層負責(zé)一種選擇谦炬,也就是一層屏蔽一種變化,這樣很容易造成復(fù)雜的類層次結(jié)構(gòu)。
3.5 思考抽象工廠模式##
- 抽象工廠模式的本質(zhì)
抽象工廠模式的本質(zhì):選擇產(chǎn)品簇的實現(xiàn)键思。
工廠方法是選擇單個產(chǎn)品的實現(xiàn)础爬,雖然一個類里面可以有多個工廠方法,但是這些方法之間一般是沒有聯(lián)系的吼鳞,即使看起來像有聯(lián)系看蚜。
但是抽象工廠著重的就是為一個產(chǎn)品簇選擇實現(xiàn),定義在抽象工廠里面的方法通常是有聯(lián)系的赖条,它們都是產(chǎn)品的某一部分或者是相互依賴的失乾。如果抽象工廠里面只定義一個方法,直接創(chuàng)建產(chǎn)品纬乍,那么就退化成為工廠方法了。
- 何時選用抽象工廠模式
建議在如下情況中纽竣,選用抽象工廠模式:
如果希望一個系統(tǒng)獨立于它的產(chǎn)品的創(chuàng)建茧泪,組合和表示的時候,換句話說队伟,希望一個系統(tǒng)只是知道產(chǎn)品的接口,而不關(guān)心實現(xiàn)的時候港令。
如果一個系統(tǒng)要由多個產(chǎn)品系列中的一個來配置的時候锈颗,換句話說顷霹,就是可以動態(tài)的切換產(chǎn)品簇的時候。
如果要強調(diào)一系列相關(guān)產(chǎn)品的接口击吱,以便聯(lián)合使用它們的時候淋淀。
3.6 相關(guān)模式##
- 抽象工廠模式和工廠方法模式
這兩個模式既有區(qū)別,又有聯(lián)系覆醇,可以組合使用朵纷。
工廠方法模式一般是針對單獨的產(chǎn)品對象的創(chuàng)建,而抽象工廠模式注重產(chǎn)品簇對象的創(chuàng)建永脓,這是它們的區(qū)別柴罐。
如果把抽象工廠創(chuàng)建的產(chǎn)品簇簡化,這個產(chǎn)品簇就只有一個產(chǎn)品憨奸,那么這個時候的抽象工廠跟工廠方法是差不多的革屠,也就是抽象工廠可以退化成工廠方法,而工廠方法又可以退化成簡單工廠,這是它們的聯(lián)系似芝。
在抽象工廠的實現(xiàn)中那婉,還可以使用工廠方法來提供抽象工廠的具體實現(xiàn),也就是說它們可以組合使用党瓮。
- 抽象工廠模式和單例模式
這兩個模式可以組合使用详炬。
在抽象工廠模式里面,具體的工廠實現(xiàn)寞奸,在整個應(yīng)用中呛谜,通常一個產(chǎn)品系列只需要一個實例就可以了,因此可以把具體的工廠實現(xiàn)成為單例枪萄。