### 一、模式定義
簡單工廠模式(Simple Factory Pattern):又稱為靜態工廠方法(Static Factory Method)模式,它屬于類創建型模式(同屬于創建型模式的還有工廠方法模式,抽象工廠模式,單例模式,建造者模式)。在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類。
### 二、模式結構

從上圖可以看出,簡單工廠模式由三部分組成:具體工廠、具體產品和抽象產品。
-
工廠類(Creator)角色:擔任這個角色的是簡單工廠模式的核心,含有與應用緊密相關的商業邏輯。工廠類在客戶端的直接調用下創建產品對象,它往往由一個具體Java類實現。
-
抽象產品(AbstractProduct)角色:擔任這個角色的類是由簡單工廠模式所創建的對象的父類,或它們共同擁有的接口。抽象產品角色可以用一個Java接口或者Java抽象類實現。
-
具體產品(ConcreteProduct)角色:簡單工廠模式所創建的任何對象都是這個角色的實例,具體產品角色由一個具體Java類實現。
### 三、模式動機
使用簡單工廠模式可以將產品的“消費”和生產完全分開,客戶端只需要知道自己需要什么產品,如何來使用產品就可以了,具體的產品生產任務由具體的工廠類來實現。工廠類根據傳進來的參數生產具體的產品供消費者使用。這種模式使得更加利于擴展,當有新的產品加入時僅僅需要在工廠中加入新產品的構造就可以了。
### 四、實例分析
話說有一位土豪,他家有三輛汽車——Benz奔馳、Bmw寶馬、Audi奧迪,還雇了司機為他開車。不過,土豪坐車時總是怪怪的:上Benz車后跟司機說“開奔馳車!”坐上Bmw后他說“開寶馬車!”,坐上Audi說“開奧迪車!”。你一定說:這人有病!直接說開車不就行了?! 而當把土豪的行為放到我們程序設計中來時,會發現這是一個普遍存在的現象。幸運的是,這種有病的現象在OO(面向對象)語言中可以避免了。下面就以Java語言為例來講解一下如何避免這種問題。
下面就是剛剛的故事所描述的場景,這段代碼真的是有病啊。
這個是Audi車的類,在這個類中有driveAudi的方法。
~~~
package com.myfactory.pre1;
public class Audi {
public Audi(){
System.out.println("Create a Audi");
}
public void driveAudi(){
System.out.println("Audi start engine");
}
}
~~~
這個是Benz車的類,在這個類中有driveBenz 的方法。
~~~
package com.myfactory.pre1;
public class Benz {
public Benz(){
System.out.println("Create a Benz");
}
public void driveBenz(){
System.out.println("Benz start engine");
}
}
~~~
這個是Bmw車的類,在這個類中有driveBmw 的方法。
~~~
package com.myfactory.pre1;
public class Bmw {
public Bmw(){
System.out.println("Create a Bmw");
}
public void driveBmw(){
System.out.println("Bmw start engine");
}
}
~~~
主程序調用剛剛創建的三輛車的類。
~~~
package com.myfactory.pre1;
public class Main {
public static void main(String[] args) {
//今天想做奧迪車
Audi audi = new Audi();
//開奧迪車
audi.driveAudi();
}
}
~~~
學過程序設計的人都能看出來上邊這段代碼的結構非常的不好。有兩點,一是,不管是Audi,Benz還是Bmw,大家不都是車嗎,為什么不直接創建一個Car的父類,然后讓其他的汽車的子類來繼承呢,二是,不管是什么車肯定都能開啊,干嘛要分那么清楚呢,直接在父類中寫一個drive方法讓子類來實現不就好了,如果每輛汽車都有自己名字命名的drive方法,那如果main中稍微寫錯一點程序就跑不起來了。
所有針對以上的問題我們可以做如下的優化。
創建了一個新的類Car,作為所有汽車的父類,定義了一個抽象的drive方法,具體的實現由子類來實現。
~~~
package com.myfactory.pre2;
public abstract class Car {
abstract void drive();
}
~~~
這是改進后的Audi類實現了父類的drive方法。
~~~
package com.myfactory.pre2;
public class Audi extends Car{
public Audi(){
System.out.println("Create a Audi");
}
public void drive(){
System.out.println("Audi start engine");
}
}
~~~
Benz和Bmw都是類似的,這里為了節約篇幅我就不再貼出來了,最后回復上完整的源碼。
這是改進后的新客戶端:
~~~
package com.myfactory.pre2;
public class Main {
public static void main(String[] args) {
//今天想做奧迪車
Car car = new Audi();
//開車
car.drive();
}
}
~~~
這個方案看似完美但其實還是存在巨大的隱患。我提供對的實例是比較簡單的,但實際應用中創建對象時可能不是一句話就能解決的事,比如創建奧迪車時需要發動機的型號,輪胎的大小,玻璃的尺寸,并且尺寸可能是由一系列復雜的運算計算出來的,所以如果把創建對象的任務放到客戶端中,就是使客戶端顯得非常的臃腫,所以我們可以借助簡單工廠模式進行改進。
我們創建一個工廠類,這個工廠類專門負責建造各種汽車,代碼如下:
~~~
package com.myfactory.simplefactory;
public class Driver {
public static Car getCar(String type) throws Exception{
if(type.equals("Benz")){
return new Benz();
}else if(type.equals("Audi")){
return new Audi();
}else if(type.equals("Bmw")){
return new Bmw();
}else{
throw new Exception();
}
}
}
~~~
各種汽車的類還是不變,這里不再貼出代碼,此時的客戶端發生了變化。
~~~
package com.myfactory.simplefactory;
public class Main {
public static void main(String[] args) throws Exception {
//今天想做奧迪車
Car car = Driver.getCar("Audi");
//開車
car.drive();
}
}
~~~
我們看到,土豪不再需要自己new對象了,而是告訴Driver類今天我想要開什么車,Driver就會自動的將想要的汽車創建出來,然后土豪坐在車里只需要說開車就好了,這樣不就符合我們的邏輯了嗎。
在上面的那個例子中,Driver類就是我們說的工廠,他用if-else語句來判斷需要創建什么類型的對象(當然也可以使用switch語句),這就是我們一直說的簡單工廠模式。
### 五、模式優點
- 工廠類含有必要的判斷邏輯,可以決定在什么時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的責任,而僅僅“消費”產品;簡單工廠模式通過這種做法實現了對責任的分割,它提供了專門的工廠類用于創建對象。
- 客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對于一些復雜的類名,通過簡單工廠模式可以減少使用者的記憶量。
- 通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
- 當需要引入新的產品是不需要修改客戶端的代碼,只需要添加相應的產品類并修改工廠類就可以了,所以說從產品的角度上簡單工廠模式是符合“開-閉”原則的。
### 六、模式缺點
- 由于工廠類集中了所有產品創建邏輯,工廠類一般被我們稱作“全能類”或者“上帝類”,因為所有的產品創建他都能完成,這看似是好事,但仔細想想是有問題的。比如全國上下所有的事情都有國家主義一個人干會不會有問題,當然有!一旦不能正常工作,整個系統都要受到影響。
- 使用簡單工廠模式將會增加系統中類的個數,在一定程序上增加了系統的復雜度和理解難度。
- 系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,在產品類型較多時,有可能造成工廠邏輯過于復雜,不利于系統的擴展和維護。所以說從工廠的角度來說簡單工廠模式是不符合“開-閉”原則的。
- 簡單工廠模式由于使用了靜態工廠方法,造成工廠角色無法形成基于繼承的等級結構。
### 七、適用場景
在以下情況下可以使用簡單工廠模式:
1、工廠類負責創建的對象比較少:由于創建的對象較少,不會造成工廠方法中的業務邏輯太過復雜。
2、客戶端只知道傳入工廠類的參數,對于如何創建對象不關心:客戶端既不需要關心創建細節,甚至連類名都不需要記住,只需要知道類型所對應的參數。
### 八、模式在JDK中的應用
JDK類庫中廣泛使用了簡單工廠模式,如工具類java.text.DateFormat,它用于格式化一個本地日期或者時間。
~~~
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale
locale);
~~~
源碼下載:[http://download.csdn.net/detail/xingjiarong/9294193](http://download.csdn.net/detail/xingjiarong/9294193)
- 前言
- 設計原則(一)"開-閉"原則(OCP)
- 設計原則(二)里氏替換原則(LSP)
- 設計原則(三)組合復用原則
- 設計原則(四)依賴倒置原則(DIP)
- 設計模式(一)簡單工廠模式
- 設計模式(二)工廠方法模式
- 設計模式(三)抽象工廠模式
- 設計模式(四)單例模式
- 設計模式(五)創建者模式(Builder)
- 設計模式(六)原型模式
- 設計模式(七)門面模式(Facade Pattern 外觀模式)
- 設計模式(八)橋梁模式(Bridge)
- 設計模式(九)裝飾模式(Decorator)
- 設計模式(十)適配器模式
- 設計模式(十一)策略模式
- 設計模式(十二)責任鏈模式
- 設計模式之UML(一)類圖以及類間關系(泛化 、實現、依賴、關聯、聚合、組合)
- 設計模式之橋梁模式和策略模式的區別