[TOC]
創建型模式包括工廠模式、建造者模式和單例模式。其中工廠模式又分為:
* 簡單工廠模式(又稱 靜態工廠方法模式)
* 工廠方法模式(又稱 工廠模式、虛擬構造器模式、多態工廠模式)
* 抽象工廠模式(又稱 Kit 模式)
# 1 簡單工廠模式
## 1.1 模式定義
簡單工廠模式(Simple Factory Pattern):又稱為靜態工廠方法(Static Factory Method)模式,它屬于類創建型模式。在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類。
## 1.2 模式結構
簡單工廠模式包含如下角色:
* Factory:工廠角色
負責實現創建所有實例的內部邏輯
* Product:抽象產品角色
是所創建的所有對象的父類,負責描述所有實例所共有的公共接口
* ConcreteProduct:具體產品角色
具體產品角色是創建目標,所有創建的對象都充當這個角色的某個具體類的實例。
## 1.3 代碼示例
```java
public static final Fruit getFruit(String name) {
if (name.equals("apple")) {
return new Apple();
} else if (name.equals("banana")) {
return new Banana();
} else if (name.equals("grape")) {
return new Grape();
} else {
return null;
}
}
```
## 1.4 總結
* 創建型模式對類的實例化過程進行了抽象,能夠將對象的創建與對象的使用過程分離。
* 簡單工廠模式的要點在于:當你需要什么,只需要傳入一個正確的參數,就可以獲取你所需要的對象,而無須知道其創建細節。
* 簡單工廠模式最大的優點在于實現對象的創建和對象的使用分離,將對象的創建交給專門的工廠類負責,但是其最大的缺點在于工廠類不夠靈活,增加新的具體產品需要修改工廠類的判斷邏輯代碼,而且產品較多時,工廠方法代碼將會非常復雜。
* 簡單工廠模式適用情況包括:工廠類負責創建的對象比較少;客戶端只知道傳入工廠類的參數,對于如何創建對象不關心。
# 2 工廠方法模式
## 2.1 模式定義
工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,屬于類創建型模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
## 2.2 模式結構
工廠方法模式包含如下角色:
* Product:抽象產品
定義產品的接口,產品對象的共同父類或者接口
* ConcreteProduct:具體產品
實現抽象產品接口
* Factory:抽象工廠
聲明工廠方法
* ConcreteFactory:具體工廠
抽象工廠類的子類,實現抽象工廠定義的工廠方法
## 2.3 代碼示例
```java
interface FruitFactory {
Fruit createFruit();
}
public class AppleFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Apple();
}
}
public class BananaFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Banana();
}
}
public class GrapeFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Grape();
}
}
```
## 2.4 總結
* 工廠方法模式包含四個角色:抽象產品是定義產品的接口,是工廠方法模式所創建對象的超類型,即產品對象的共同父類或接口;具體產品實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,它們之間往往一一對應;抽象工廠中聲明了工廠方法,用于返回一個產品,它是工廠方法模式的核心,任何在模式中創建對象的工廠類都必須實現該接口;具體工廠是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,并可由客戶調用,返回一個具體產品類的實例。
* 工廠方法模式是簡單工廠模式的進一步抽象和推廣。由于使用了面向對象的多態性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的創建,而是將具體創建工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的接口,而不負責產品類被實例化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
* 工廠方法模式的主要優點是增加新的產品類時無須修改現有系統,并封裝了產品對象的創建細節,系統具有良好的靈活性和可擴展性;其缺點在于增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,在一定程度上增加了系統的復雜性。
* 工廠方法模式適用情況包括:一個類不知道它所需要的對象的類;一個類通過其子類來指定創建哪個對象;將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定。
# 3 抽象工廠模式
## 3.1 模式定義
抽象工廠模式(Abstract Factory Pattern):提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱為 Kit 模式,屬于對象創建型模式。
## 3.2 模式結構
抽象工廠模式包含如下角色:
* AbstractFactory:抽象工廠
用于聲明生成抽象產品的方法
* ConcreteFactory:具體工廠
實現了抽象工廠聲明的生成抽象產品的方法,生成一組具體產品,這些產品構成了一個產品族,每一個產品都位于某個產品等級結構中
* AbstractProduct:抽象產品
為每種產品聲明接口,在抽象產品中定義了產品的抽象業務方法
* Product:具體產品
定義具體工廠生產的具體產品對象,實現抽象產品接口中定義的業務方法。
## 3.3 代碼示例
```java
// 抽象工廠
public interface ComputerCompany {
Keyboard createKeyboard();
Mouse createMouse();
}
// 具體工廠
public class AppleCompany implements ComputerCompany {
@Override
public Keyboard createKeyboard() {
return new AppleKeyboard();
}
@Override
public Mouse createMouse() {
return new AppleMouse();
}
}
public class DellCompany implements ComputerCompany {
@Override
public Keyboard createKeyboard() {
return new DellKeyboard();
}
@Override
public Mouse createMouse() {
return new DellMouse();
}
}
// 抽象產品
public abstract Keyboard {
public abstract void print();
}
public abstract Mouse {
public abstract void scroll();
}
// 具體產品
public class AppleKeyboard extends Keyboard {
@Override
public void print() {
}
}
public class DellKeyboard extends Keyboard {
@Override
public void print() {
}
}
public class AppleMouse extends Mouse {
@Override
public void scroll() {
}
}
public class DellMouse extends Mouse {
@Override
public void scroll() {
}
}
```
## 3.4 總結
* Keyboard 和 AppleKeyboard、Dellkeyboard 的關系為父類、子類的關系,它們在一個產品等級結構中。AppleKeyboard 和 AppleMouse 則屬于同一個產品族。
* 抽象工廠模式與工廠方法模式最大的區別在于,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構。
* 抽象工廠模式的主要優點是隔離了具體類的生成,使得客戶并不需要知道什么被創建,而且每次可以通過具體工廠類創建一個產品族中的多個對象,增加或者替換產品族比較方便,增加新的具體工廠和產品族很方便;
* 主要缺點在于增加新的產品等級結構很復雜,需要修改抽象工廠和所有的具體工廠類,對“開閉原則”的支持呈現傾斜性。
* 抽象工廠模式適用情況包括:一個系統不應當依賴于產品類實例如何被創建、組合和表達的細節;系統中有多于一個的產品族,而每次只使用其中某一產品族;屬于同一個產品族的產品將在一起使用;系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴于具體實現。
# 4 工廠模式的退化
當抽象工廠模式中每一個具體工廠類只創建一個產品對象,也就是只存在一個產品等級結構時,抽象工廠模式退化成工廠方法模式;
當工廠方法模式中抽象工廠與具體工廠合并,提供一個統一的工廠來創建產品對象,并將創建對象的工廠方法設計為靜態方法時,工廠方法模式退化成簡單工廠模式。
# 5 建造者模式
## 5.1 模式定義
造者模式(Builder Pattern):將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
建造者模式是一步一步創建一個復雜的對象,它允許用戶只通過指定復雜對象的類型和內容就可以構建它們,用戶不需要知道內部的具體構建細節。建造者模式屬于對象創建型模式。根據中文翻譯的不同,建造者模式又可以稱為生成器模式。
## 5.2 模式結構
* Product:產品角色
被構建的復雜對象,包含多個組成部件
* Builder:抽象建造者
為創建一個產品對象的各個部件指定抽象接口
* ConcreteBuilder:具體建造者
實現了抽象建造者接口,實現各個部件的構造和裝配方法,定義并明確它所創建的復雜對象,也可以提供一個方法返回創建好的復雜產品對象
* Director:指揮者
負責安排復雜對象的建造次序,指揮者與抽象建造者之間存在關聯關系,可以在其 construct() 建造方法中調用建造者對象的部件構造與裝配方法,完成復雜對象的建造
## 5.3 代碼示例
```java
// 產品角色
public class Phone {
private String mBrand;
private String mOS;
private String mMemory;
// getter and setter ...
}
// 抽象建造者
public abstract class PhoneBuilder {
protected Phone mPhone = new Phone();
public abstract void buildBrand();
public abstract void buildOS();
public abstract void buildMemory();
public Phone createPhone() {
return mPhone;
}
}
// 具體建造者
public class ApplePhoneBuilder extends PhoneBuilder {
@Override
public void buildBrand() {
mPhone.setBrand("Apple");
}
@Override
public void buildOS() {
mPhone.setOS("iOS");
}
@Override
public void buildMemory() {
mPhone.setMemory("1G");
}
}
public class NexusPhoneBuilder extends PhoneBuilder {
@Override
public void buildBrand() {
mPhone.setBrand("Nexus");
}
@Override
public void buildOS() {
mPhone.setOS("Android");
}
@Override
public void buildMemory() {
mPhone.setMemory("3G");
}
}
// 指揮者
public class PhoneDirector {
private PhoneBuilder mPhoneBuilder;
public void setPhoneBuilder(PhoneBuilder phoneBuilder) {
mPhoneBuilder = phoneBuilder;
}
public Phone construct() {
mPhoneBuilder.buildBrand();
mPhoneBuilder.buildMemory();
mPhoneBuilder.buildOS();
return mPhoneBuilder.createPhone();
}
}
// 使用
ApplePhoneBuilder applePhoneBuilder = new ApplePhoneBuilder();
NexusPhoneBuilder nexusPhoneBuilder = new NexusPhoneBuilder();
PhoneDirector director = new PhoneDirector();
director.setPhoneBuilder(applePhoneBuilder);
Phone iPhone = director.construct();
director.setPhoneBuilder(nexusPhoneBuilder);
Phone Nexus = director.construct();
```
## 5.4 總結
* 在建造者模式的結構中引入了一個指揮者類,該類的作用主要有兩個:一方面它隔離了客戶與生產過程;另一方面它負責控制產品的生成過程。指揮者針對抽象建造者編程,客戶端只需要知道具體建造者的類型,即可通過指揮者類調用建造者的相關方法,返回一個完整的產品對象。
* 建造者模式的主要優點在于客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象,每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,符合“開閉原則”,還可以更加精細地控制產品的創建過程;
* 其主要缺點在于由于建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,因此其使用范圍受到一定的限制,如果產品的內部變化復雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大。
* 建造者模式適用情況包括:需要生成的產品對象有復雜的內部結構,這些產品對象通常包含多個成員屬性;需要生成的產品對象的屬性相互依賴,需要指定其生成順序;對象的創建過程獨立于創建該對象的類;隔離復雜對象的創建和使用,并使得相同的創建過程可以創建不同類型的產品。
# 6 單例模式
## 6.1 模式定義
單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例,這個類稱為單例類,它提供全局訪問的方法。
單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式是一種對象創建型模式。單例模式又名單件模式或單態模式。
## 6.2 模式結構
* Singleton:單例
在單例類的內部實現只生成一個實例,同時它提供一個靜態的工廠方法,讓客戶可以使用它的唯一實例;為了防止在外部對其實例化,將其構造函數設計為私有。
## 6.3 代碼示例
```java
public class Singleton {
private static volatile Singleton sInstance;
public static synchronized Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
private Singleton() {}
}
```
## 6.4 總結
* 單例模式的目的是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。單例類擁有一個私有構造函數,確保用戶無法通過 new 關鍵字直接實例化它。除此之外,該模式中包含一個靜態私有成員變量與靜態公有的工廠方法。該工廠方法負責檢驗實例的存在性并實例化自己,然后存儲在靜態成員變量中,以確保只有一個實例被創建。
* 單例模式的主要優點在于提供了對唯一實例的受控訪問并可以節約系統資源;其主要缺點在于因為缺少抽象層而難以擴展,且單例類職責過重。
* 單例模式適用情況包括:系統只需要一個實例對象;客戶調用類的單個實例只允許使用一個公共訪問點。
## 6.5 幾種單例模式的寫法
1、懶漢式(線程不安全)
```java
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
懶漢式寫法使用了懶加載模式,也就是在使用類實例(調用getInstance方法)時,才會進行初始化。但卻存在一個問題,當多個線程并行調用getInstance方法時就會產生多個實例。
2、懶漢式(線程安全)
```java
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
上面的寫法解決了多個線程并行調用創建多個實例的問題,但是并不高效。因為每次只能有一個線程調用getInstance方法使用實例,但實際上只有第一次調用創建時需要加鎖,后面并不需要加鎖。
3、雙重校驗鎖
```java
public class Singleton {
// 聲明成 volatile
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
是一種使用同步塊加鎖的方法,首先在同步塊外檢查一次,如果instance!=null直接返回實例;如果instance==null就進入同步塊內,再次檢查instance是否為空,因為可能會有多個線程一起進入同步塊外部的if,為空則創建實例。
但是由于`instance = new Singleton();`并非是一個原子操作,創建實例的過程如下:
a、給instance分配內存區域
b、調用Singleton的構造函數來初始化成員變量
c、將instance指向分配的內存空間
但在JVM的及時編譯器中存在指令重排序的優化,步驟b和c的執行順序并不能保證,可能為a-b-c的順序,也可能a-c-b的順序執行。如果是a-c-b的執行順序的話,在c執行完成后,b執行前,假設其他線程拿到instance實例后會發現并未初始化而報錯。
我們在這里將instance變量聲明為volatile的,volatile關鍵字有兩個作用:一是保證內存可見性(線程之間),二是禁止指令重排優化。此處主要是用到volatile的第二個特性,在volatile變量的賦值操作后會有一個內存屏障,讀操作不會被重排到內存屏障前。也就是讀操作一定會在對volatile變量的寫操作執行完成后才會進行。
注:Java5以前的volatile依然無法禁止指令重排優化。
4、餓漢式
```java
public class Singleton{
//類加載時就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
```
這種寫法在類第一次加載到內存中時就會進行初始化實例,是線程安全的。但是某些場景下無法使用,比如需要先調用類的某個方法設置參數給類,然后根據參數再創建實例。
5、靜態內部類寫法
```java
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
```
SingletonHolder是私有的,除了getInstance沒有方法可以訪問它,因此是懶加載;同時JVM本身的機制保證了線程安全問題;讀取實例的時候不會進行同步,沒有性能上的缺陷;也不依賴JDK版本。
6、枚舉Enum
```java
public enum EasySingleton{
INSTANCE;
}
```
可以通過EasySingleton.INSTANCE直接來訪問實例
## 參考文章
[如何正確地寫出單例模式](http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/)
[volatile關鍵字及其作用](https://blog.csdn.net/u010255818/article/details/65633033)
- 導讀
- Java知識
- Java基本程序設計結構
- 【基礎知識】Java基礎
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設計結構
- 對象與類
- 【基礎知識】對象與類
- 【專題分析】Java類加載過程
- 【面試清單】對象與類
- 泛型
- 【基礎知識】泛型
- 【面試清單】泛型
- 集合
- 【基礎知識】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎知識】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識
- Activity
- 【基礎知識】Activity
- 【專題分析】運行時權限
- 【專題分析】使用Intent打開三方應用
- 【源碼分析】Activity的工作過程
- 【面試清單】Activity
- 架構組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數據綁定
- 【面試清單】架構組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發機制
- 【源碼分析】按鍵事件分發機制
- 【源碼分析】Android窗口機制
- 【面試清單】界面
- 動畫和過渡
- 【基礎知識】動畫和過渡
- 【面試清單】動畫和過渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺任務
- 應用數據和文件
- 基于網絡的內容
- 多線程與多進程
- 【基礎知識】多線程與多進程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進程
- 應用優化
- 【專題分析】布局優化
- 【專題分析】繪制優化
- 【專題分析】內存優化
- 【專題分析】啟動優化
- 【專題分析】電池優化
- 【專題分析】包大小優化
- 【面試清單】應用優化
- Android新特性
- 【專題分析】狀態欄、ActionBar和導航欄
- 【專題分析】應用圖標、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標識符的最佳做法
- 開源庫源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識
- Flutter
- 原生開發與跨平臺開發
- 整體歸納
- 狀態及狀態管理
- 零碎知識點
- 添加Flutter到現有應用
- Git知識
- Git命令
- .gitignore文件
- 設計模式
- 創建型模式
- 結構型模式
- 行為型模式
- RxJava
- 基礎
- Linux知識
- 環境變量
- Linux命令
- ADB命令
- 算法
- 常見數據結構及實現
- 數組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊列
- 算法時間復雜度
- 常見算法思想
- 其他技術
- 正則表達式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識
- 開發歸納
- Android零碎問題
- 其他零碎問題
- 開發思路