設計模式 #2 (工廠模式)
文章中所有工程代碼和UML
建模文件都在我的這個GitHub
的公開庫--->DesignPattern。Star
來一個好嗎?秋梨膏!
簡述 :提供一種創建對象的最佳方式。
在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。
面向接口(抽象)編程?聞到內味了嗎?7大設計原則中的依賴倒置原則、迪米特法則、接口隔離原則。
當然不止用到了一個原則,一般設計模式都是多個設計原則的集合體。
簡單工廠模式
簡述:創建產品接口,需要產品時,利用工廠進行創建即可。
# 反例 :
public class negtive {
/*===============服務端======================*/
interface Food{
void eat();
}
static class Noodles implements Food{
@Override
public void eat() {
System.out.println("吃面條。。。。。");
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
Food food01 = new Noodles();
food01.eat();
}
}
UML
類圖如下:
這時候,產品來改需求來了,“哥,你先把刀放下。咱們現在這 Noodles
改名了,得改個特牛逼的名字Spaghetti
,讓用戶記住咱們這是西餐意大利面。”
這時候,因為你原有設計是上面的反例,你得能從修改服務端的源代碼開始,再修改客戶端源代碼。以后再有改名這類事,你還要把刀拿出來放桌上給產品看。
這種設計過於脆弱,因為這樣服務端源代碼和客戶端源代碼是耦合的,改變會牽一發而動全身。
# 正例:
public class postive {
/*===============服務端======================*/
interface Food{
void eat();
}
static class Spaghetti implements Food {
@Override
public void eat() {
System.out.println("吃西餐面條。。。。。");
}
}
static class FoodFactory {
public Food getFood(int num){
Food food =null;
switch (num){
case 1 :
food = new Spaghetti();
}
return food;
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory = new FoodFactory();
Food food01 = foodFactory.getFood(1);
food01.eat();
}
}
UML
類圖如下:
通過這樣一個正例,把創建對象的代碼全交給服務端處理,將服務端代碼和客戶端代碼進行了解耦。以后產品再找你聊天是不是可以暫時把刀收起來了?
這樣做的好處,不只是服務端開發人員受益,當服務端代碼修改時,客戶端也不知道,也不需要知道。
這樣的設計模式並不是十全十美的,任何一種設計模式都不會是十全十美的。只是根據業務邏輯在各方面進行取舍。
簡單工廠模式的缺點:
- 客戶必須記住工廠中常量和具體產品的映射關系。
- 一旦產品品種體量增大到一定程度,工廠類將變得非常臃腫。
- 最致命的缺陷,增加產品時,就要修改工廠類。違反開閉原則。
工廠方法模式
簡述:為了進行擴展,不違反開閉原則。
這里是基於簡單工廠模式進行改進。
# 正例:
public class postive {
/*===============服務端======================*/
//-----------------------產品--------------------
interface Food{
void eat();
}
static class Spaghetti implements Food {
@Override
public void eat() {
System.out.println("吃西餐面條。。。。。");
}
}
//新增產品
static class Rice implements Food {
@Override
public void eat() {
System.out.println("吃米飯。。。。。");
}
}
//--------------------------工廠-----------------------
interface FoodFactory {
Food getFood();
}
static class SpaghettiFactory implements FoodFactory{
@Override
public Food getFood() {
return new Spaghetti();
}
}
//新增產品工廠
static class RiceFactory implements FoodFactory{
@Override
public Food getFood() {
return new Rice();
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory = new SpaghettiFactory();
Food food01 = foodFactory.getFood();
food01.eat();
}
}
UML
類圖如下:
針對簡單工廠違反開閉原則的這一缺陷,工廠方法模式進行優化。可以看到此時再去增加產品,不再需要修改工廠類,而是增加相應的產品類和工廠類即可。這是符合開閉原則的。
這里就會有聰明的小問號有很多朋友了:
- 如果源代碼作者修改相關工廠類的類名,那這時候調用工廠類的客戶端代碼就需要修改了,這不如簡單工廠呢?
首先這里要明確一個概念,工廠類在實際使用中,是相當於接口類的,接口類一般不允許進行修改(非必須),工廠類作者有責任,有義務保證工廠類的類名是穩定的,也就是說,工廠類是比產品類更加穩定的。
- 既然使我們后面自己擴展的
Rice
類,為什么不直接實例化它,直接使用。我們就是作者,為什么不能直接使用?
這里需要擴展一下,有時候一個產品類並不是孤立的,它和其他類一起組成一個服務框架。
下面增加一些類:
/*===============服務端======================*/
//------------------------產品質檢流程-----------------------、
static class QualityInspection {
public void checking(FoodFactory foodFactory){
System.out.println("我是人肉質檢員。。。。。准備開吃 -_- ");
Food food = foodFactory.getFood();
food.eat();
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
FoodFactory foodFactory01 = new SpaghettiFactory();
FoodFactory foodFactory02 = new RiceFactory();
QualityInspection inspection = new QualityInspection();
inspection.checking(foodFactory02);
inspection.checking(foodFactory01);
UML
類圖如下:
這時候,如果Rice
沒有他的工廠類,甚至都沒辦法參加質檢,那還怎么賣?
所以編寫工廠類並不只是單純為了實例化某些產品類,而是能讓配套服務通過工廠接口,得以調用工廠創建產品實例。
有的小朋友大大的眼睛里還有疑惑:那為什么QualityInspection
的checking
方法不直接調用Food
接口再進行產品的實例化呢?
這時候回到簡單工廠模式,產品類不同於工廠類,它是善變的,它會隨着需求的變化而變化,這時候,直接依賴產品類的各種方法,將需要被修改,違反開閉原則。這是死路,小朋友別杠了。哈哈哈。
當然,工廠方法模式也是有缺陷的:
- 當業務需要的類型變多,目前只有食物,當產生飲料,日用品等類別時,我們又要創建新的工廠來實現,造成代碼重復臃腫。
抽象工廠模式
針對工廠方法模式的缺陷,抽象工廠模式將進行改進,一個工廠負責創建一個產品簇的對象。
關於產品簇:是指多個存在內在聯系的或者存在邏輯關系的產品。

簡述:在抽象工廠模式中,接口是負責創建一個相關對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。
抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創建其他工廠。該超級工廠又稱為其他工廠的工廠。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
# 正例:
public class postive {
/*===============服務端======================*/
//-----------------------產品--------------------
/*----------------螺絲---------------------*/
interface Screw{
void createScrew();
}
static class Screw_06 implements Screw {
@Override
public void createScrew() {
System.out.println("create Screw_06 666666。。。。。");
}
}
static class Screw_08 implements Screw {
@Override
public void createScrew() {
System.out.println("create Screw_08 8888888。。。。。");
}
}
/*----------------螺母---------------------*/
interface Nut{
void createNut();
}
static class Nut_06 implements Nut {
@Override
public void createNut() {
System.out.println("create Nut_06 666666。。。。。");
}
}
static class Nut_08 implements Nut {
@Override
public void createNut() {
System.out.println("create Nut_08 8888888。。。。。");
}
}
//--------------------------工廠-----------------------
interface ComponentsFactory {
Screw getScrew();
Nut getNut();
}
/*----------------6號工廠---------------------*/
static class Factory_666 implements ComponentsFactory {
@Override
public Screw getScrew() {
return new Screw_06();
}
@Override
public Nut getNut() {
return new Nut_06();
}
}
/*----------------8號工廠---------------------*/
static class Factory_888 implements ComponentsFactory {
@Override
public Screw getScrew() {
return new Screw_08();
}
@Override
public Nut getNut() {
return new Nut_08();
}
}
//------------------------產品質檢流程-----------------------、
static class QualityInspection {
public void checking(ComponentsFactory Factory){
System.out.println("我是人肉質檢員。。。。。等待產出零件 -_- ");
Screw screw = Factory.getScrew();
Nut nut = Factory.getNut();
screw.createScrew();
nut.createNut();
System.out.println("開始質檢.......");
System.out.println(" ");
}
}
/*=================客戶端===================*/
public static void main(String[] args) {
ComponentsFactory Factory01 = new Factory_666();
ComponentsFactory Factory02 = new Factory_888();
QualityInspection inspection = new QualityInspection();
inspection.checking(Factory01);
inspection.checking(Factory02);
}
}
UML
類圖如下:
可以看到,如果在需要進行一種N
號螺絲或者螺母的擴展,只需要增加一個實現N
號螺絲或者螺母接口的產品類,利用一個新增N
號工廠進行創建即可。
可以看到,抽象工廠仍然保持着簡單工廠模式和工廠方法模式的優點:
- 服務端代碼和客戶端代碼是低耦合的。(簡單工廠模式)
- 所有這一切動作都是新增,不是修改,符合開閉原則。
還新增了一個特有的優點:
- 抽象工廠有效減少了工廠的數量,一個工廠就生產同一個產品簇的產品。
這下產品來改需求,是不是還可以笑嘻嘻跟他聊會天了?
再次強調,一個抽象工廠負責創建同一個產品簇的對象。而產品簇是指多個存在內在聯系的或者存在邏輯關系的產品。也就是6
號工廠只生產6
號的零部件,不負責生產8
號零部件。不能不同產品簇的產品混合到一個工廠中進行生產。
缺陷:當增加產品簇時(增加6
、 8
號螺帽的生產),這時候就要修改以前工廠(6
、 8
號工廠)的源代碼了。
總結就是:
- 當產品簇比較固定時,考慮使用抽象工廠。
- 當產品經常變動時,不建議使用抽象工廠。