創(chuàng)建型模式

本文主要介紹一下設(shè)計模式中的創(chuàng)建型模式。

開篇之前,先說一下,什么是設(shè)計模式,一言以蔽之,設(shè)計模式是代碼設(shè)計經(jīng)驗的總結(jié)。設(shè)計模式的原理非常簡單,但是也絕不可能通過一篇文章或者一本書來完全掌握(在學(xué)習(xí)任何一種知識的時候都應(yīng)該博采眾長,取舍有度,而不是盯著一本書或者一篇文章看,偏信一家之言,要如魯迅所說:“運用腦髓,放出眼光,自己來拿!”)。其次,死記硬背設(shè)計模式的UML圖或者示例代碼沒有意義,和OOP編程一下,設(shè)計模式本質(zhì)上是一種思想,須得領(lǐng)會后方能靈活運用。

設(shè)計模式主要基于以下兩個面向?qū)ο蟮木幊趟枷耄?/p>

  • 面向接口編程
  • 組合優(yōu)于繼承

設(shè)計模式還有所謂的六大原則,這里擇其重點說明一下:

  • 對擴展開放,對修改關(guān)閉
  • 任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。理解這個原則:只有當派生類可以替換掉基類,且軟件單位的功能不受到影響時,基類才能真正被復(fù)用,而派生類也能夠在基類的基礎(chǔ)上增加新的行為。
  • 使用多個隔離的接口,比使用單個接口要好
  • 一個實體應(yīng)當盡量少地與其他實體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對獨立

創(chuàng)建型模式,顧名思義,就是一種創(chuàng)建對象的方法。

創(chuàng)建型模式有五種:工廠、抽象工廠、單例、建造者、原型。

工廠

工廠模式在工廠、策略和橋接這篇文章中已經(jīng)介紹過,這里不再贅述。

抽象工廠

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創(chuàng)建其他工廠。該超級工廠又稱為其他工廠的工廠。

先看圖:

image

再看代碼:

public interface Shape {
   void draw();
}

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public interface Color {
   void fill();
}

public class Green implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

public class Blue implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Blue::fill() method.");
   }
}

public abstract class AbstractFactory {
   public abstract Color getColor(String color);
   public abstract Shape getShape(String shape) ;
}

public class ShapeFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      return null;
   }
}

public class ColorFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }
      return null;
   }
}

public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
 
      //獲取形狀工廠
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
 
      //獲取形狀為 Circle 的對象
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //調(diào)用 Circle 的 draw 方法
      shape1.draw();
 
      //獲取形狀為 Rectangle 的對象
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //調(diào)用 Rectangle 的 draw 方法
      shape2.draw();
      
      //獲取形狀為 Square 的對象
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //調(diào)用 Square 的 draw 方法
      shape3.draw();
 
      //獲取顏色工廠
      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
 
      //獲取顏色為 Red 的對象
      Color color1 = colorFactory.getColor("RED");
 
      //調(diào)用 Red 的 fill 方法
      color1.fill();
 
      //獲取顏色為 Green 的對象
      Color color2 = colorFactory.getColor("Green");
 
      //調(diào)用 Green 的 fill 方法
      color2.fill();
 
      //獲取顏色為 Blue 的對象
      Color color3 = colorFactory.getColor("BLUE");
 
      //調(diào)用 Blue 的 fill 方法
      color3.fill();
   }
}

總結(jié):抽象工廠相較于工廠而言,多了一個產(chǎn)品族的概念,實際上就是在工廠接口下再做一個細分。

單例

單例模式在多線程下的單例模式這篇文章已經(jīng)有過初步介紹,這里詳細列出:

  1. 懶漢式,線程不安全,懶加載

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
      
        public static Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    
  2. 懶漢式,線程安全,懶加載

    加鎖會影響效率。

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
        public static synchronized Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    
  3. 餓漢式,線程安全,非懶加載常用

    沒有加鎖,執(zhí)行效率會提高。

    類加載時就初始化,浪費內(nèi)存:雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

    public class Singleton {  
        private static Singleton instance = new Singleton();  
        private Singleton (){}  
        public static Singleton getInstance() {  
         return instance;  
        }  
    }
    
  4. 雙重校驗鎖,線程安全,懶加載

    4和2比較起來,可以在保證線程安全的情況下依然保持高性能。

    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;  
        }  
    }
    
  5. 使用靜態(tài)內(nèi)部類,線程安全,懶加載

    這種方式能達到雙檢鎖方式一樣的功效,但實現(xiàn)更簡單。對靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。這種方式同樣利用了 classloader 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那么 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有通過顯式調(diào)用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗?instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。

    public class Singleton {  
        private static class SingletonHolder {  
         private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){
            
        }  
        public static final Singleton getInstance() {  
         return SingletonHolder.INSTANCE;  
        }  
    }
    
  6. 使用枚舉,線程安全,非懶加載

    這種實現(xiàn)方式還沒有被廣泛采用,但這是實現(xiàn)單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創(chuàng)建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。不能通過 reflection attack 來調(diào)用私有構(gòu)造方法。

    public enum Singleton {  
        INSTANCE;  
        public void whateverMethod() {
        }  
    }
    

    總結(jié)一下:加鎖降低執(zhí)行效率,非懶加載產(chǎn)生垃圾對象,浪費內(nèi)存。根據(jù)具體場景決定使用哪種方式。

建造者

建造者模式(Builder Pattern)使用多個簡單的對象一步一步構(gòu)建成一個復(fù)雜的對象。這種模式的應(yīng)用非常廣泛,比如Java中的StringBuilder,Android中的AlertDialog.Builder。

用一個組裝計算機的例子來說明。

先上圖:這張圖里的聚合和依賴關(guān)系標注有誤,具體結(jié)合代碼理解。

聚合關(guān)系(成員):如果A由B聚合成,表現(xiàn)為A包含有B的全局對象,但是B對象可以不在A創(chuàng)建的時刻創(chuàng)建。

組合關(guān)系(成員):如果A由B組成,表現(xiàn)為A包含有B的全局對象,并且B對象在A創(chuàng)建的時刻創(chuàng)建。

依賴關(guān)系(局部):如果A依賴于B,則B體現(xiàn)為局部變量,方法的參數(shù)、或靜態(tài)方法的調(diào)用。

所以這里應(yīng)該是Director由Builder組合而成,MacBookBuilder由MacBook組合而成。圖上有誤。

image

再上代碼:

public class MacBook{ 
    protected MacBook() {
    }
    
    public void setBoard(String board){
        mBoard=board;
    }
 
    public void setDisplay(String display) {
        this.mDisplay = display;
    }
}

// 可以使用抽象類,也可以使用接口
public abstract class Builder {
    abstract void buildBoard(String board);
    abstract void buildDisplay(String display);
    abstract MacBook build();
}

public class MacBookBuilder extends Builder {
 
    private MacBook mMacBook = new MacBook();
    
    @Override
    void buildBoard(String board) {
        mMacBook.setBoard(board);
    }
 
    @Override
    void buildDisplay(String display) {
        mMacBook.setDisplay(display);
    }
 
    @Override
    Computer build() {
        return mMacBook;
    }
}

public class Director {
    Builder mBuilder=null;
 
    public Director(Builder builder) {
        this.mBuilder = builder;
    }
 
    public void construct(String board,String display){
        mBuilder.buildDisplay(display);
        mBuilder.buildBoard(board);
    }
}

// 具體使用
public class Test {
 
    public static void main(String[] args){
        // 定義每個建造環(huán)節(jié)的具體實現(xiàn)
        Builder builder = new MacBookBuilder();
        
        // 設(shè)計建造流程,將各個環(huán)節(jié)組裝排序
        Director director = new Director(builder);
        director.construct("英特爾主板", "Retina顯示器");
 
        MacBook macBook = builder.build();
    }
 
}

原型

原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象,同時又能保證性能。這種模式是實現(xiàn)了一個原型接口,該接口用于創(chuàng)建當前對象的克隆。當直接創(chuàng)建對象的代價比較大時,則采用這種模式。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫,以此來減少數(shù)據(jù)庫調(diào)用。

用代碼直觀理解一下:

public abstract class Shape implements Cloneable {
   
   private String id;
   protected String type;
   
   abstract void draw();
   
   public String getType(){
      return type;
   }
   
   public String getId() {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
   
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

public class Rectangle extends Shape {
 
   public Rectangle(){
     type = "Rectangle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square extends Shape {
 
   public Square(){
     type = "Square";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public class Circle extends Shape {
 
   public Circle(){
     type = "Circle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}
 
// 從數(shù)據(jù)庫獲取實體類,并把它們存儲在一個 Hashtable 中。
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap 
      = new Hashtable<String, Shape>();
 
   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }
 
   // 對每種形狀都運行數(shù)據(jù)庫查詢,并創(chuàng)建該形狀
   // shapeMap.put(shapeKey, 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 class PrototypePatternDemo {
   public static void main(String[] args) {
      ShapeCache.loadCache();
 
      Shape clonedShape = (Shape) ShapeCache.getShape("1");
      System.out.println("Shape : " + clonedShape.getType());        
 
      Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
      System.out.println("Shape : " + clonedShape2.getType());        
 
      Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
      System.out.println("Shape : " + clonedShape3.getType());        
   }
}

注意:與通過對一個類進行實例化來構(gòu)造新對象不同的是,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的。淺拷貝實現(xiàn) Cloneable,重寫,深拷貝是通過實現(xiàn) Serializable 讀取二進制流。

微信公眾號 長夜西風(fēng)

個人網(wǎng)站 http://www.cmder.info/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容