確保一個類只有一個實例,並提供該實例的全局訪問點。html
使用一個私有構造函數、一個私有靜態變量以及一個公有靜態函數來實現。java
私有構造函數保證了不能經過構造函數來建立對象實例,只能經過公有靜態函數返回惟一的私有靜態變量。git
如下實現中,私有靜態變量 uniqueInstance 被延遲實例化,這樣作的好處是,若是沒有用到該類,那麼就不會實例化 uniqueInstance,從而節約資源。github
這個實如今多線程環境下是不安全的,若是多個線程可以同時進入 if (uniqueInstance == null)
,而且此時 uniqueInstance 爲 null,那麼會有多個線程執行 uniqueInstance = new Singleton();
語句,這將致使實例化屢次 uniqueInstance。apache
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}複製代碼
線程不安全問題主要是因爲 uniqueInstance 被實例化屢次,採起直接實例化 uniqueInstance 的方式就不會產生線程不安全問題。編程
可是直接實例化的方式也丟失了延遲實例化帶來的節約資源的好處。api
public class Singleton {
//線程不安全問題主要是因爲 uniqueIntance被實例化了屢次,
//若是uniqueInstance採用直接實例化的話,就不會被實例化屢次,也就不會產生線程不安全的問題。
private static Singleton uniqueInstance=new Singleton2();
private Singleton(){
}
public static Singleton getUniqueInstance(){
return uniqueInstance;
}
}複製代碼
只須要對 getUniqueInstance() 方法加鎖,那麼在一個時間點只能有一個線程可以進入該方法, 從而避免了實例化屢次 uniqueInstance。緩存
可是當一個線程進入該方法以後,其它試圖進入該方法的線程都必須等待, 即便 uniqueInstance 已經被實例化了。這會讓線程阻塞時間過長,所以該方法有性能問題,不推薦使用。安全
public class Singleton{
//線程不安全問題主要是因爲 uniqueIntance被實例化了屢次,
//若是uniqueInstance採用直接實例化的話,就不會被實例化屢次,也就不會產生線程不安全的問題。
private static Singleton uniqueInstance;
private Singleton(){
}
//當一個線程進入該方法以後,其它試圖進入該方法的線程都必須等待
public synchronized static Singleton getUniqueInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}複製代碼
uniqueInstance 只須要被實例化一次,以後就能夠直接使用了。加鎖操做只須要對實例化那部分的代碼進行,只有當 uniqueInstance 沒有被實例化時,才須要進行加鎖。bash
雙重校驗鎖先判斷 uniqueInstance 是否已經被實例化,若是沒有被實例化,那麼纔對實例化語句進行加鎖。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}複製代碼
考慮下面的實現,也就是隻使用了一個 if 語句。 在 uniqueInstance == null 的狀況下,若是兩個線程都執行了 if 語句,那麼兩個線程都會進入 if 語句塊內。雖然在 if 語句塊內有加鎖操做,可是兩個線程都會執行 uniqueInstance = new Singleton();
這條語句,只是前後的問題,那麼就會進行兩次實例化。所以必須使用雙重校驗鎖,也就是須要使用兩個 if 語句。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}複製代碼
uniqueInstance 採用 volatile 關鍵字修飾也是頗有必要的, uniqueInstance = new Singleton();
這段代碼實際上是分爲三步執行:
可是因爲 JVM 具備指令重排的特性,執行順序有可能變成 1>3>2。指令重排在單線程環境下不會出現問題,可是在多線程環境下會致使一個線程得到尚未初始化的實例。 例如,線程 T1 執行了 1 和 3,此時 T2 調用 getUniqueInstance() 後發現 uniqueInstance 不爲空,所以返回 uniqueInstance,但此時 uniqueInstance 還未被初始化。
使用 volatile 能夠禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。
當 Singleton 類加載時,靜態內部類 SingletonHolder 沒有被加載進內存。只有當調用 getUniqueInstance()
方法從而觸發 SingletonHolder.INSTANCE
時 SingletonHolder 纔會被加載,此時初始化 INSTANCE 實例,而且 JVM 能確保 INSTANCE 只被實例化一次。
這種方式不只具備延遲初始化的好處,並且由 JVM 提供了對線程安全的支持。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}複製代碼
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 單例測試
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射獲取實例測試
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
firstName
secondName
secondName
secondName複製代碼
該實如今屢次序列化再進行反序列化以後,不會獲得多個實例。而其它實現須要使用 transient 修飾全部字段, 而且實現序列化和反序列化的方法。
該實現能夠防止反射攻擊。在其它實現中,經過 setAccessible() 方法能夠將私有構造函數的訪問級別設置爲 public,而後調用構造函數從而實例化對象,若是要防止這種攻擊,須要在構造函數中添加防止屢次實例化的代碼。該實現是由 JVM 保證只會實例化一次,所以不會出現上述的反射攻擊。
枚舉實現(最推薦使用)
public class Singleton {
private Singleton(){}
public static Singleton getUniqueInstance(){
return Singleton.INSTANCE.getSingleton();
}
private enum Singleton{
INSTANCE;
//若是打算自定義本身的方法,那麼必須在enum實例序列的最後添加一個分號。
//並且 Java 要求必須先定義 enum 實例
private Singleton singleton;
//JVM保證這個方法絕對只被調用一次
Singleton(){
singleton=new Singleton();
}
public Singleton getSingleton() {
return singleton;
}
}
}複製代碼
在建立一個對象時不向客戶暴露內部細節,並提供一個建立對象的通用接口。
簡單工廠把實例化的操做單獨放到一個類中,這個類就成爲簡單工廠類, 讓簡單工廠類來決定應該用哪一個具體子類來實例化。
這樣作能把客戶類和具體子類的實現解耦, 客戶類再也不須要知道有哪些子類以及應當實例化哪一個子類。 客戶類每每有多個,若是不使用簡單工廠,那麼全部的客戶類都要知道全部子類的細節。 並且一旦子類發生改變,例如增長子類,那麼全部的客戶類都要進行修改。
public interface Product {
}
public class ConcreteProduct implements Product {
}
public class ConcreteProduct1 implements Product {
}
public class ConcreteProduct2 implements Product {
}複製代碼
如下的 Client 類包含了實例化的代碼,這是一種錯誤的實現。若是在客戶類中存在這種實例化代碼,就須要考慮將代碼放到簡單工廠中。
public class Client {
public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
// do something with the product
}
}複製代碼
如下的 SimpleFactory 是簡單工廠實現,它被全部須要進行實例化的客戶類調用。
public class SimpleFactory {
public Product createProduct(int type) {
if (type == 1) {
return new ConcreteProduct1();
} else if (type == 2) {
return new ConcreteProduct2();
}
return new ConcreteProduct();
}
}
public class Client {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1);
// do something with the product
}
}複製代碼
幫助封裝:簡單工廠雖然很簡單,可是很是友好的幫助咱們實現了組件的封裝,而後讓組件外部能真正面向接口編程。
解耦:經過簡單工廠,把客戶類和具體子類的實現解耦。
可能增長客戶端的複雜度: 若是經過客戶端的參數來選擇具體的實現類, 那麼就必須讓客戶端能理解各個參數所表明的具體功能和含義,這會增長客戶端使用的難度, 也 部分暴露了內部實現,這種狀況能夠選用可配置的方式來實現
不方便擴展子工廠:私有化簡單工廠的構造方法,使用靜態方法來建立接口, 也就不能經過寫簡單工廠類的子類來改變建立接口的方法的行爲了。不過,一般狀況下是不須要爲簡單工廠建立子類的。
定義了一個建立對象的接口,但由子類決定要實例化哪一個類。工廠方法把實例化操做推遲到子類。
在簡單工廠中,建立對象的是另外一個類,而在工廠方法中,是由子類來建立對象。
下圖中,Factory 有一個 doSomething() 方法,這個方法須要用到一個產品對象,這個產品對象由 factoryMethod() 方法建立。該方法是抽象的,須要由子類去實現。
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}
public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
}
public class ConcreteFactory2 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct2();
}
}複製代碼
提供一個接口,用於建立 相關的對象家族 。
抽象工廠模式建立的是對象家族,也就是不少對象而不是一個對象,而且這些對象是相關的,也就是說必須一塊兒建立出來。而工廠方法模式只是用於建立一個對象,這和抽象工廠模式有很大不一樣。
抽象工廠模式用到了工廠方法模式來建立單一對象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是讓子類來實現,這兩個方法單獨來看就是在建立一個對象,這符合工廠方法模式的定義。
至於建立對象的家族這一律念是在 Client 體現,Client 要經過 AbstractFactory 同時調用兩個方法來建立出兩個對象,在這裏這兩個對象就有很大的相關性,Client 須要同時建立出這兩個對象。
從高層次來看,抽象工廠使用了組合,即 Cilent 組合了 AbstractFactory,而工廠方法模式使用了繼承。
public class AbstractProductA {
}
public class AbstractProductB {
}
public class ProductA1 extends AbstractProductA {
}
public class ProductA2 extends AbstractProductA {
}
public class ProductB1 extends AbstractProductB {
}
public class ProductB2 extends AbstractProductB {
}
public abstract class AbstractFactory {
abstract AbstractProductA createProductA();
abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA1();
}
AbstractProductB createProductB() {
return new ProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA2();
}
AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
AbstractFactory abstractFactory = new ConcreteFactory1();
AbstractProductA productA = abstractFactory.createProductA();
AbstractProductB productB = abstractFactory.createProductB();
// do something with productA and productB
}
}複製代碼
封裝一個對象的構造過程,並容許按步驟構造。 (將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。)
要實現一樣的構建過程能夠建立不一樣的表現,那麼一個天然的思路就是 先把構建過程獨立出來,在生成器模式中把它稱爲指導者, 由它來指導裝配過程,可是不負責每步具體的實現。 固然,光有指導者是不夠的,必需要有能具體實現每步的對象,在生成器模式中稱這些實現對象爲生成器。 這樣一來,指導者就是能夠重用的構建過程,而生成器是能夠被切換的具體實現。
**
* 指導者負責指導裝配過程,可是不負責每步具體的實現。
*/
public class Director {
private AbstractComputerBuilder computerBuilder;
public void setComputerBuilder(AbstractComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Product getProduct() {
return computerBuilder.getProduct();
}
public void constructComputer() {
computerBuilder.buildProduct();
computerBuilder.buildMaster();
computerBuilder.buildScreen();
computerBuilder.buildKeyboard();
computerBuilder.buildMouse();
computerBuilder.buildAudio();
}
}
/**
* 定義一個產品類
*/
public class Product {
private String master;
private String screen;
private String keyboard;
private String mouse;
private String audio;
public void setMaster(String master) {
this.master = master;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMaster() {
return master;
}
public String getScreen() {
return screen;
}
public String getKeyboard() {
return keyboard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public String getMouse() {
return mouse;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
public String getAudio() {
return audio;
}
public void setAudio(String audio) {
this.audio = audio;
}
}
/**
* 生成器的抽象類
* 負責具體實現每步的對象
*/
public abstract class AbstractComputerBuilder {
protected Product product;
public Product getProduct() {
return product;
}
public void buildProduct(){
product=new Product();
System.out.println("生產出一臺電腦");
}
public abstract void buildMaster();
public abstract void buildScreen();
public abstract void buildKeyboard();
public abstract void buildMouse();
public abstract void buildAudio();
}
public class HPComputerBuilder extends AbstractComputerBuilder{
@Override
public void buildMaster() {
// TODO Auto-generated method stub
product.setMaster("i7,16g,512SSD,1060");
System.out.println("(i7,16g,512SSD,1060)的惠普主機");
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
product.setScreen("4K");
System.out.println("(4K)的惠普顯示屏");
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
product.setKeyboard("cherry 青軸機械鍵盤");
System.out.println("(cherry 青軸機械鍵盤)的鍵盤");
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
product.setMouse("MI 鼠標");
System.out.println("(MI 鼠標)的鼠標");
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
product.setAudio("飛利浦 音響");
System.out.println("(飛利浦 音響)的音響");
}
}
public class DELLComputerBuilder extends AbstractComputerBuilder{
@Override
public void buildMaster() {
// TODO Auto-generated method stub
product.setMaster("i7,32g,1TSSD,1060");
System.out.println("(i7,32g,1TSSD,1060)的戴爾主機");
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
product.setScreen("4k");
System.out.println("(4k)的dell顯示屏");
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
product.setKeyboard("cherry 黑軸機械鍵盤");
System.out.println("(cherry 黑軸機械鍵盤)的鍵盤");
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
product.setMouse("MI 鼠標");
System.out.println("(MI 鼠標)的鼠標");
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
product.setAudio("飛利浦 音響");
System.out.println("(飛利浦 音響)的音響");
}
}
/**
* 指導者就是能夠重用的構建過程,
* 而生成器是能夠被切換的具體實現
*/
public class Client {
public static void main(String[] args) {
AbstractComputerBuilder computerBuilder=new HPComputerBuilder();
AbstractComputerBuilder computerBuilder2=new DELLComputerBuilder();
Director director=new Director();
director.setComputerBuilder(computerBuilder);
director.constructComputer();
//獲取PC
Product pc=director.getProduct();
director.setComputerBuilder(computerBuilder2);
director.constructComputer();
Product pc2=director.getProduct();
}
}複製代碼
生成器的調用順序:
如下是一個簡易的 StringBuilder 實現,參考了 JDK 1.8 源碼。
public class AbstractStringBuilder {
protected char[] value;
protected int count;
public AbstractStringBuilder(int capacity) {
count = 0;
value = new char[capacity];
}
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
}
public class StringBuilder extends AbstractStringBuilder {
public StringBuilder() {
super(16);
}
@Override
public String toString() {
// Create a copy, don't share the array return new String(value, 0, count); } } public class Client { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); final int count = 26; for (int i = 0; i < count; i++) { sb.append((char) ('a' + i)); } System.out.println(sb.toString()); } } abcdefghijklmnopqrstuvwxyz複製代碼
使用原型實例指定要建立對象的類型,經過複製這個原型來建立新對象。
public abstract class Prototype {
abstract Prototype myClone();
}
public class ConcretePrototype extends Prototype {
private String filed;
public ConcretePrototype(String filed) {
this.filed = filed;
}
@Override
Prototype myClone() {
return new ConcretePrototype(filed);
}
@Override
public String toString() {
return filed;
}
}
public class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype("abc");
Prototype clone = prototype.myClone();
System.out.println(clone.toString());
}
}
複製代碼
abc複製代碼