抽象工廠模式
抽象工廠模式用來解決產(chǎn)品族的實(shí)例化問題。比如說現(xiàn)在有個(gè)家居設(shè)計(jì)軟件,通過軟件模擬房間,擺放各種虛擬的家具,看效果如何。我們可以放入電視柜、茶幾、餐桌、床等等。這一系列的家具就叫做產(chǎn)品族。產(chǎn)品族面臨的問題是,當(dāng)一個(gè)產(chǎn)品族切換到另外一個(gè)產(chǎn)品族時(shí),如何讓代碼的修改最小。也就是說如何做到開閉原則。
想把設(shè)計(jì)好的方案從簡(jiǎn)約現(xiàn)代切換到歐式風(fēng)格家具,怎么才能做到修改最?。咳绻捎煤?jiǎn)單工廠,那么每種產(chǎn)品都對(duì)應(yīng)一個(gè)工廠,工廠負(fù)責(zé)產(chǎn)出不同風(fēng)格的產(chǎn)品。設(shè)計(jì)方案中用到 n 種產(chǎn)品就要修改 n 處代碼。這顯然不是最佳的方法。此時(shí),我們需要抽象工廠模式來解決這個(gè)問題。抽象工廠模式中,每個(gè)工廠的實(shí)現(xiàn)負(fù)責(zé)生產(chǎn)自己產(chǎn)品族的產(chǎn)品。示意圖如下:
1. 實(shí)現(xiàn)抽象工廠
為了便于理解和展示,我們假設(shè)只有兩種家具----椅子和桌子。
首先定義每種家具的接口,只有一個(gè)方法用來獲取家具說明。
椅子:
public interface Chair {
void getChairIntroduction();
}
桌子:
public interface Desk {
void getDeskIntroduction();
}
以椅子為例,我們分別實(shí)現(xiàn)簡(jiǎn)約現(xiàn)代和歐式兩種風(fēng)格。
簡(jiǎn)約現(xiàn)代風(fēng)格椅子:
public class ModernStyleChair implements Chair {
@Override
public void getChairIntroduction() {
System.out.println("這是一個(gè)現(xiàn)代簡(jiǎn)約風(fēng)格的椅子");
}
}
歐式風(fēng)格椅子:
public class EuropeanStyleChair implements Chair {
@Override
public void getChairIntroduction() {
System.out.println("這是一個(gè)歐式風(fēng)格的椅子");
}
}
桌子也有兩種實(shí)現(xiàn),代碼這里省略。
產(chǎn)品我們已經(jīng)編寫完成。接下來我們來看看工廠的代碼。
首先我們定義一個(gè)家具工廠接口,可以生產(chǎn)椅子和桌子:
public interface FurnitureFactory {
Chair createChair();
Desk createDesk();
}
由于我們支持兩種不同的風(fēng)格,所以我們編寫兩個(gè)實(shí)現(xiàn)類。
簡(jiǎn)約風(fēng)格家具工廠:
public class ModernFurnitureFactory implements FurnitureFactory{
@Override
public Chair createChair() {
return new ModernStyleChair();
}
@Override
public Desk createDesk() {
return new ModernStyleDesk();
}
}
歐式風(fēng)格家具工廠:
public class EuropeanFurnitureFactory implements FurnitureFactory{
@Override
public Chair createChair() {
return new EuropeanStyleChair();
}
@Override
public Desk createDesk() {
return new EuropeanStyleDesk();
}
}
上面的代碼中,每種工廠各自實(shí)現(xiàn)如何生產(chǎn)兩種不同的家具。
客戶端代碼如下:
public class Client {
public static void main(String[] args) {
FurnitureFactory furnitureFactory = new EuropeanFurnitureFactory();
Chair chair = furnitureFactory.createChair();
Desk desk = furnitureFactory.createDesk();
chair.getChairIntroduction();
desk.getDeskIntroduction();
}
}
客戶端代碼中,我們實(shí)例化的是歐式家具工廠,那么所生產(chǎn)的椅子和桌子應(yīng)該是歐式風(fēng)格。執(zhí)行后輸出如下:
這是一個(gè)歐式風(fēng)格的椅子
這是一個(gè)歐式風(fēng)格的桌子
和我們的預(yù)期相符。如果想要更換產(chǎn)品族,從現(xiàn)代簡(jiǎn)約切換到歐式,我們只需要修改一處代碼。
FurnitureFactory furnitureFactory = new ModernFurnitureFactory();
僅通過更換抽象工廠的實(shí)現(xiàn)即可實(shí)現(xiàn)。修改后執(zhí)行結(jié)果如下:
這是一個(gè)現(xiàn)代簡(jiǎn)約風(fēng)格的椅子
這是一個(gè)現(xiàn)代簡(jiǎn)約風(fēng)格的桌子
可以看到已經(jīng)切換到簡(jiǎn)約風(fēng)格的產(chǎn)品族。這個(gè)過程中并不需要改任何產(chǎn)品使用的代碼。
如果增加別的風(fēng)格產(chǎn)品族,只需要新建新風(fēng)格的產(chǎn)品族產(chǎn)品,增加新風(fēng)格產(chǎn)品族的工廠實(shí)現(xiàn)即可。
類圖:
2. 抽象工廠優(yōu)缺點(diǎn)
2.1 優(yōu)點(diǎn)
- 分離了產(chǎn)品類和客戶端類:客戶端只依賴抽象的產(chǎn)品接口。此外,如何生產(chǎn)產(chǎn)品被封裝在工廠內(nèi)部;
- 方便切換產(chǎn)品族:客戶端代碼只需要初始化一次工廠實(shí)現(xiàn)。這意味著在切換產(chǎn)品族的時(shí)候,只需要修改一行代碼,換一個(gè)工廠實(shí)現(xiàn)即可;
- 保證產(chǎn)品的一致性:使用抽象工廠,可以保證你從相同工廠生產(chǎn)的產(chǎn)品都屬于同一個(gè)產(chǎn)品族。不會(huì)出現(xiàn)椅子是現(xiàn)代簡(jiǎn)約風(fēng)格,而桌子是歐式風(fēng)格的情況。
2.2 缺點(diǎn)
添加新的產(chǎn)品時(shí),改動(dòng)較多。例子從兩個(gè)維度定義產(chǎn)品,一是不同產(chǎn)品,比如桌子、椅子。另外是不同族,例如現(xiàn)代簡(jiǎn)約和歐式。使用抽象工廠,優(yōu)化了產(chǎn)品族,也就是第二個(gè)維度變化的難度。但是當(dāng)添加新的產(chǎn)品時(shí)改動(dòng)就會(huì)比較多。比如我們要添加一個(gè)新的產(chǎn)品是電視柜。那么需要修改抽象工廠,添加生產(chǎn)電視柜的方法。此外,有幾種工廠的實(shí)現(xiàn),我們就需要修改幾個(gè)類,添加具體的生產(chǎn)實(shí)現(xiàn)。
3. 抽象工廠適用場(chǎng)景
- 你的系統(tǒng)中,需要使用不同產(chǎn)品族中的某一個(gè)產(chǎn)品族來操作。 比如說DB源。如果想切換DB,只需要切換DB源即可,其他代碼基本上不需要改動(dòng);
- 你的系統(tǒng)中,需要保證某些產(chǎn)品的一致性。 比如操作系統(tǒng)的外觀,當(dāng)切換到夜間模式時(shí),所有的組件都會(huì)換為夜間模式風(fēng)格。
4. 小結(jié)
抽象工廠可以做到一組產(chǎn)品的使用和生產(chǎn)相分離。通過抽象工廠模式,我們切換一組產(chǎn)品族的,只需要更換抽象工廠實(shí)現(xiàn)即可。由于產(chǎn)品生產(chǎn)被分離出去,所以添加新的產(chǎn)品族完全通過擴(kuò)展來實(shí)現(xiàn)的。很好的實(shí)現(xiàn)了開閉原則。如果你要生產(chǎn)的產(chǎn)品很多,而且是一個(gè)產(chǎn)品族。并且面臨不同產(chǎn)品族切換的情況。那么可以考慮通過抽象工廠來實(shí)現(xiàn)。