建立型模式(二)
關於建立型模式中工廠方法模式,抽象工廠模式在上一篇文章中我們瞭解了一下,今天我們看一下單例模式,建造者模式,原型模式。
單例模式
單例模式我們用的很多,我想大家也都理解這種模式,就是保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
通常單例模式有5種實現方式:懶漢式;餓漢式;雙檢鎖;靜態內部類;列舉。個人推薦使用列舉,因為使用反序列化攻擊和反射也不會建立新的物件出來,雖然其他實現方法也有辦法避免這種攻擊但是需要寫一定量的程式碼
1、懶漢式 :必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
2、餓漢式 :類載入時就初始化,浪費記憶體。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
3、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)這種方式採用雙鎖機制,安全且在多執行緒情況下能保持高效能。
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
4、登記式/靜態內部類。這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用於靜態域的情況,雙檢鎖方式可在例項域需要延遲初始化時使用。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
5、列舉。這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
建造者模式
建造者模式(Builder Pattern)使用多個簡單的物件一步一步構建成一個複雜的物件。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
一個 Builder 類會一步一步構造最終的物件。該 Builder 類是獨立於其他物件的。
優點: 1、建造者獨立,易擴充套件。 2、便於控制細節風險。
缺點: 1、產品必須有共同點,範圍有限制。 2、如內部變化複雜,會有很多的建造類。
建造者模式舉例:去肯德基點餐,我們可以認為點餐就屬於一個建造訂單的過程。我們點餐的順序是無關的,點什麼東西也是沒有要求的,可以單點,也可以點套餐,也可以套餐加單點,但是最後一定要點確認來完成訂單。
public class OrderBuilder{
private Burger mBurger;
private Suit mSuit;
//單點漢堡,num為數量
public OrderBuilder burger(Burger burger, int num){
mBurger = burger;
}
//點套餐,實際中套餐也可以點多份
public OrderBuilder suit(Suit suit, int num){
mSuit = suit;
}
//完成訂單
public Order build(){
Order order = new Order();
order.setBurger(mBurger);
order.setSuit(mSuit);
return order;
}
}
另外適用於快速失敗,在 build 時可以做校驗,如果不滿足必要條件,則可以直接丟擲建立異常,在 OkHttp3 中的 Request.Builder 中就是這樣用的。
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
例如訂單要求價格至少達到 30 塊:
//完成訂單
public Order build(){
Order order = new Order();
order.setBurger(mBurger);
order.setSuit(mSuit);
if(order.getPrice() < 30){
throw new BuildException("訂單金額未達到30元");
}
return order;
}
另外,在構建時如果有必傳引數和可選引數,可以為 Builder 類新增建構函式來保證必傳引數不會遺漏,例如在構建一個 http 請求時, url 是必傳的:
public class RequestBuilder{
private final String mUrl;
private Map<String, String> mHeaders = new HashMap<String, String>();
private RequestBuilder(String url){
mUrl = url;
}
public static RequestBuilder newBuilder(String url){
return new RequestBuilder(url);
}
public RequestBuilder addHeader(String key, String value){
mHeaders.put(key, value);
}
public Request build(){
Request request = new Request();
request.setUrl(mUrl);
request.setHeaders(mHeaders);
return request;
}
}
原型模式
原型模式(Prototype Pattern)是用於建立重複的物件,同時又能保證效能。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。
優點: 1、效能提高。 2、逃避建構函式的約束。
缺點: 1、配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支援序列化的間接物件,或者引用含有迴圈結構的時候。 2、必須實現 Cloneable 介面。
原型模式中有三個登場角色:
原型角色:定義用於複製現有例項來生成新例項的方法;
// 以貼主示例程式碼為例
implements Cloneable // 1.(抽象類或者介面)實現 java.lang.Cloneable 介面
public Shape clone(); // 2.定義複製現有例項來生成新例項的方法
具體原型角色:實現用於複製現有例項來生成新例項的方法
public Shape clone() {// 2.實現複製現有例項來生成新例項的方法(也可以由超類完成)
Shape clone = null;
try {
clone = (Shape) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
使用者角色:維護一個登錄檔,並提供一個找出正確例項原型的方法。最後,提供一個獲取新例項的方法,用來委託複製例項的方法生成新例項。
private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();//維護一個登錄檔public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
public static Shape getShape(String shapeId) {//提供一個獲取新例項的方法
Shape cachedShape = shapeMap.get(shapeId);//提供一個找出正確例項原型的方法
return (Shape) cachedShape.clone();//委託複製例項的方法生成新例項。
}
更多文章請關注公眾號:每天學Java。想獲得更多最新面試提醒請進入小程式:每天學Java
公眾號二維碼: 小程式二維碼: