Java 設計模式(二)創建型模式

  本系列文章共分爲五篇:
    設計模式的分類與原則
    創建型模式
    結構型模式
    行爲型模式
    設計模式的區別
  本篇將介紹創建者模式,創建型模式的主要關注點是“怎樣創建對象,使對象的創建與使用分離開來”。

一、單例模式

1.1 單例模式定義

  指一個類只有一個實例,且該類能自行創建這個實例的一種模式。

1.2 單例模式特點

  單例類只有一個實例對象。
  該單例對象必須由單例類自行創建。
  單例類對外提供一個訪問該單例的全局訪問點。

1.3 單例模式主要角色

  單例類:包含一個實例且能自行創建這個實例的類。
  訪問類:使用單例的類。

1.4 單例模式實現方式

  單例模式的實現方式較多,主要方式如下:

1.4.1懶漢式單例

  該模式的特點是類加載時沒有生成單例,只有當第一次調用getlnstance方法時纔去創建這個單例。代碼示例如下:

public class Singleton{
    //保證instance在所有線程中同步
    private static volatile Singleton instance=null; 
    //private避免類在外部被實例化   
    private Singleton(){}                               
    public static synchronized Singleton getInstance(){
        //getInstance方法前加同步
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

  如果編寫的是多線程程序,則不能刪除上例代碼中的關鍵字 volatile 和 synchronized,否則將存在線程非安全的問題。如果不刪除這兩個關鍵字就能保證線程安全,但是每次訪問時都要同步,會影響性能,且消耗更多的資源,這是懶漢式單例的缺點。

1.4.2 餓漢式單例

  餓漢式單例在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以是線程安全的,可以直接用於多線程而不會出現問題。代碼示例如下:

public class Singleton {  
    //在調用getInstance之前就創建一個static對象
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}
1.4.3 雙重校驗鎖單例

  該方式既不會在類加載時就初始化單例對象,又合理縮小了synchronized鎖的影響範圍,安全且在多線程情況下能保持高性能。代碼示例如下:

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;  
    }  
}
1.4.4 靜態內部類單例

  該方式通過創建一個靜態內部類,在靜態內部類中實例化單例,進而達到了延遲實例化的目的。因爲外部類加載時並不需要立即加載內部類,內部類不被加載則不去初始化單例,故而不佔內存。同時,靜態內部類中final修飾單例,也達到了安全的目的。代碼示例如下:

public class Singleton {  
    private static class SingletonHolder {  
         private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
         return SingletonHolder.INSTANCE;  
    }  
}

1.5 單例模式應用場景

  單例模式通常適用的場景:
   1)某類只要求生成一個對象的時候。
   2)當對象需要被共享的場合。由於單例模式只允許創建一個對象,共享該對象可以節省內存,並加快對象訪問速度。
   3)當某類需要頻繁實例化,而創建的對象又頻繁被銷燬的時候,如多線程的線程池、網絡連接池等。

二、工廠方法模式

  被創建的對象稱爲“產品”,把創建產品的對象稱爲“工廠”。如果要創建的產品不多,只要一個工廠類就可以完成,這種模式叫“簡單工廠模式"。本節介紹的“工廠方法模式”是對簡單工廠模式的進一步抽象化,其好處是可以使系統在不修改原來代碼的情況下引進新的產品。

2.1 工廠方法模式定義

  定義一個創建產品對象的工廠接口,將產品對象的實際創建工作推遲到具體子工廠類當中。

2.2 工廠方法模式特點

  優點:
   1)只需要知道具體工廠的名稱就可得到所要的產品,無須知道產品的具體創建過程。
   2)在系統增加新的產品時只需要添加具體產品類和對應的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則。
  缺點:
   每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。
  工廠方法的缺點可以通過和反射結合來改進

2.3 工廠方法模式主要角色

  抽象工廠:提供了創建產品的接口。
  具體工廠:主要是實現抽象工廠中的抽象方法,完成具體產品的創建。
  抽象產品:定義了產品的規範,描述了產品的主要特性和功能。
  具體產品:實現了抽象產品角色所定義的接口,由具體工廠來創建,它同具體工廠之間一一對應。

2.4 工廠方法模式實現方式

  工廠方法模式的實現,以播放不同公司的不同類型電影爲例。產品有兩種:動作電影和喜劇電影,工廠也有兩種:Fox公司和WBP公司,從不同的工廠裏可以生成不同類型的電影,從而達到多樣產品類型的目的。代碼示例如下:

//抽象產品:電影
public interface  Movie {
	void play();
}

//具體產品1:動作電影
public class ActionMovie implements Movie {
	private String factory;
	public ActionMovie(String factory){
      this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ActionMovie is playing");	
	}
}

//具體產品2:喜劇電影
public class ComedyMovie implements Movie {
	private String factory;
	public ComedyMovie(String factory){
		this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ComedyMovie is playing");
	}
}

//抽象工廠
public interface Factory {
    Movie getActionMovie();
    Movie getComedyMovie();
}

//具體工廠1:Fox公司
public class FoxFactory implements Factory{
    public Movie getActionMovie(){  return new ActionMovie("Fox");  }
    public Movie getComedyMovie(){	return new ComedyMovie("Fox");  }
}

//具體工廠2:WBP公司
public class WBPFactory implements Factory{
    public Movie getActionMovie(){	return new ActionMovie("WBP");  }
    public Movie getComedyMovie(){	return new ComedyMovie("WBP");  }
}

//測試類
public class FactoryTest {
    public static void main(String[] args) {  
    	Factory foxFactory = new FoxFactory();  
        Movie actionMovie = foxFactory.getActionMovie();  
        actionMovie.play();  
        Movie comedyMovie = foxFactory.getComedyMovie();  
        comedyMovie.play(); 
    	Factory WBPFactory = new WBPFactory();  
        actionMovie = WBPFactory.getActionMovie();  
        actionMovie.play();  
        comedyMovie = WBPFactory.getComedyMovie();  
        comedyMovie.play(); 
    }  
}

  結果示例:

Fox ActionMovie is playing
Fox ComedyMovie is playing
WBP ActionMovie is playing
WBP ComedyMovie is playing

2.5 工廠方法模式應用場景

  工廠方法模式通常適用的場景:
   1)客戶只知道創建產品的工廠名,而不知道具體的產品名。
   2)創建對象的任務由多個具體子工廠中的某一個完成,而抽象工廠只提供創建產品的接口。
   3)客戶不關心創建產品的細節,只關心產品的品牌。

三、抽象工廠模式

  上一節的工廠方法模式只能產生一類產品,如果需要產生多類產品,就要使用抽象工廠模式。

3.1 抽象工廠模式定義

  一種爲訪問類提供一個創建一組相關或相互依賴對象的接口,且訪問類無須指定所要產品的具體類就能得到同族的不同等級的產品的模式結構。

3.2 抽象工廠模式特點

  優點:
   1)可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
   2)當增加一個新的產品族時不需要修改原代碼,滿足開閉原則。
  缺點:當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。

3.3 抽象工廠模式主要角色

  抽象工廠:提供了創建產品的接口,可以創建多個不同類別的產品。
  具體工廠:主要是實現抽象工廠中的多個抽象方法,完成具體產品的創建。
  抽象產品:定義了產品的規範,描述了產品的主要特性和功能,抽象工廠模式有多個抽象產品。
  具體產品:實現了抽象產品角色所定義的接口,由具體工廠來創建,它 同具體工廠之間是多對一的關係。

3.4 抽象工廠模式實現方式

  抽象工廠模式的實現,此處依然以影視作品爲例,不同的產品爲:電視劇和電影,工廠爲不同的公司福克斯公司和華納兄弟公司。和上個章節的功能相比,多了生成“電視劇”產品的功能。代碼示例如下:

//抽象產品1:電影
public interface  Movie {
	void play();
}

//具體產品1:動作電影
public class ActionMovie implements Movie {
	private String factory;
	public ActionMovie(String factory){
      this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ActionMovie is playing");	
	}
}

//具體產品2:喜劇電影
public class ComedyMovie implements Movie {
	private String factory;
	public ComedyMovie(String factory){
		this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ComedyMovie is playing");
	}
}

//抽象產品2:電視劇
public interface TVSeries {
	void play();
}

//具體產品2:動作電視劇
public class ActionTVSeries  implements TVSeries{
	private String factory;
	public ActionTVSeries(String factory){
      this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ActionTVSeries is playing");	
	}
}

//具體產品2:喜劇電視劇
public class ComedyTVSeries implements TVSeries{
	private String factory;
	public ComedyTVSeries(String factory){
      this.factory = factory;
	}
	public void play() {
		System.out.println(factory+" ComedyTVSeries is playing");	
	}
}

//抽象工廠
public interface Factory {
    Movie getActionMovie();
    Movie getComedyMovie();
    TVSeries getActionTVSeries();
    TVSeries getComedyTVSeries();
}

//具體工廠1:20世紀 福克斯公司
public class FoxFactory implements Factory{
    public Movie getActionMovie(){   return new ActionMovie("Fox");  }
    public Movie getComedyMovie(){   return new ComedyMovie("Fox");  }
	public TVSeries getActionTVSeries() {	return new ActionTVSeries("Fox");  }
	public TVSeries getComedyTVSeries() {	return new ComedyTVSeries("Fox");  }
}

//具體工廠2:華納兄弟公司
public class WBPFactory implements Factory{
    public Movie getActionMovie(){	return new ActionMovie("WBP");   }
    public Movie getComedyMovie(){	return new ComedyMovie("WBP");   }
	public TVSeries getActionTVSeries() {	return new ComedyTVSeries("WBP");  }
	public TVSeries getComedyTVSeries() {	return new ComedyTVSeries("WBP");  }
}

//測試類
public class FactoryTest {
    public static void main(String[] args) {  
    	Factory foxFactory = new FoxFactory();  
        Movie actionMovie = foxFactory.getActionMovie();  
        actionMovie.play();  
        Movie comedyMovie = foxFactory.getComedyMovie();  
        comedyMovie.play(); 
        TVSeries actionTVSeries = foxFactory.getActionTVSeries();
        actionTVSeries.play();
        TVSeries comedyTVSeries = foxFactory.getComedyTVSeries();
        comedyTVSeries.play();
    	Factory WBPFactory = new WBPFactory();  
        actionMovie = WBPFactory.getActionMovie();  
        actionMovie.play();  
        comedyMovie = WBPFactory.getComedyMovie();  
        comedyMovie.play(); 
        actionTVSeries = WBPFactory.getActionTVSeries();
        actionTVSeries.play();
        comedyTVSeries = WBPFactory.getComedyTVSeries();
        comedyTVSeries.play();
    }  
}

  結果示例:

Fox ActionMovie is playing
Fox ComedyMovie is playing
Fox ActionTVSeries is playing
Fox ComedyTVSeries is playing
WBP ActionMovie is playing
WBP ComedyMovie is playing
WBP ComedyTVSeries is playing
WBP ComedyTVSeries is playing

3.5 抽象工廠模式應用場景

  抽象工廠模式通常適用的場景:
   1)當需要創建的對象是一系列相互關聯或相互依賴的產品族時。
   2)系統中有多個產品族,但每次只使用其中的某一族產品。
   3)系統中提供了產品的類庫,且所有產品的接口相同,客戶端不依賴產品實例的創建細節和內部結構。

四、建造者模式

  建造者模式常常用來創建複雜的產品,此模式中,會有一個專業的建造者(Build)類負責複雜產品的具體創建過程,從而達到對象創建和使用分離的目的。

4.1 建造者模式定義

  指將一個複雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示。

4.2 建造者模式特點

  優點:
   1)各個具體的建造者相互獨立,有利於系統的擴展。
   2)客戶端不必知道產品內部組成的細節,便於控制細節風險。
  缺點:
   1)產品的組成部分必須相同,這限制了其使用範圍。
   2)如果產品的內部變化複雜,該模式會增加很多的建造者類。

4.3 建造者模式主要角色

  產品:包含多個組成部件的複雜對象。
  抽象建造者:它是一個包含創建產品各個子部件的抽象方法的接口,通常還包含一個返回複雜產品的方法。
  具體建造者:實現 Builder 接口,完成複雜產品的各個部件的具體創建方法。
  指揮者:它調用建造者對象中的部件構造與裝配方法完成複雜對象的創建,在指揮者中不涉及具體產品的信息。

4.4 建造者模式實現方式

  此處以一個較複雜的產品創建過程爲例,該產品包括partA、partB、partC三個模塊,這三個模塊需要分開創建,代碼示例如下:

//包含複雜組件的產品類
class Product{
    private String partA;
    private String partB;
    private String partC;
    public void setPartA(String partA){
        this.partA=partA;
    }
    public void setPartB(String partB){
        this.partB=partB;
    }
    public void setPartC(String partC){
        this.partC=partC;
    }
    public void show(){
    	System.out.println("partA:"+partA+"\npartB:"+partB+"\npartC:"+partC);	
    }
}

//抽象建造者,一般定義各個組件的建造過程和返回對象接口
public abstract class Builder{
    //創建產品對象
    protected Product product=new Product();
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();
    //返回產品對象
    public Product getResult(){
        return product;
    }
}

//具體建造者
public class ConcreteBuilder extends Builder{
    public void buildPartA(){
        product.setPartA("AAA");
    }
    public void buildPartB(){
        product.setPartB("BBB");
    }
    public void buildPartC(){
        product.setPartC("CCC");
    }
}

//指揮者
public class Director{
    private Builder builder;
    public Director(Builder builder){
        this.builder=builder;
    }
    //產品構建與組裝方法
    public Product construct(){
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.getResult();
    }
}

//測試類
public class BuildTest{
    public static void main(String[] args){
        Builder builder=new ConcreteBuilder();
        Director director=new Director(builder);
        Product product=director.construct();
        product.show();
    }
}

  結果示例:

partA:AAA
partB:BBB
partC:CCC

4.5 建造者模式應用場景

  建造者模式通常適用的場景:
   1)創建的對象較複雜,由多個部件構成,各部件面臨着複雜的變化,但構件間的建造順序是穩定的。
   2)創建複雜對象的算法獨立於該對象的組成部分以及它們的裝配方式,即產品的構建過程和最終的表示是獨立的。

五、原型模式

  在有些系統中,存在大量相同或相似對象的創建問題,如果用傳統的構造函數來創建對象,會比較複雜且耗時耗資源,此時就需要用原型模式來創建對象。

5.1 原型模式定義

  用一個已經創建的實例作爲原型,通過複製該原型對象來創建一個和原型相同或相似的新對象。

5.2 原型模式特點

  優點:
   1)性能提高。
   2)逃避構造函數的約束。
  缺點:
   1)對於現有類,進行克隆可能實現起來較爲複雜。

5.3 原型模式主要角色

  抽象原型類:規定了具體原型對象必須實現的clone接口。
  具體原型類:實現抽象原型類的clone方法,它是可被複制的對象。
  訪問類:使用具體原型類中的clone方法來複制新的對象。

5.4 原型模式實現方式

  原型模式的實現,有兩種方式:深克隆與淺克隆。淺克隆是創建一個新對象,新對象的屬性和原來對象完全相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的內存地址;深克隆是創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。代碼示例如下:

5.4.1 淺克隆實現
//抽象原型,原生的Cloneable接口
public interface Cloneable {
}
//具體原型
public class ConcretePrototype implements Cloneable {
    private String name;
    private int id;
    public Object clone(){
        ConcretePrototype prototype = null;
		try {
			prototype = (ConcretePrototype)super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
        return prototype;
    }
    public String getName() {    return name;    }
    public void setName(String name) {   
        this.name = name;    
    }
	public int getId() {	return id;   }
	public void setId(int id) {
		this.id = id;
	}
}

//訪問類&測試類
public class PrototypeTest {
    public static void main(String[] args) {  
    	ConcretePrototype prototype = new ConcretePrototype();
        prototype.setName("exp");
        ConcretePrototype prototype2 = (ConcretePrototype) prototype.clone();
    	System.out.println("prototype is equals prototype2?"+prototype.equals(prototype2));
    }
}

  結果示例:

prototype is equals prototype2?false
5.4.2 深克隆實現

  深克隆需要用序列化的方式來實現,即要實現Serializable接口。示例代碼如下:

/*具體原型*/
public class ConcretePrototype implements Serializable {
    private String name;
    private int id;
    public ConcretePrototype clone(){
    	//將對象寫入流中
    	ByteArrayOutputStream bao = new ByteArrayOutputStream();
    	ObjectOutputStream oos;
    	ObjectInputStream ois;
		try {
			oos = new ObjectOutputStream(bao);
			oos.writeObject(this);
	    	//將對象取出來
	    	ByteArrayInputStream bi = new ByteArrayInputStream(bao.toByteArray());
	    	ois = new ObjectInputStream(bi);
	    	try {
				return (ConcretePrototype)ois.readObject();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
    }
    public String getName() {    return name;   }
    public void setName(String name) {
        this.name = name;
    }
	public int getId() {	return id;    }
	public void setId(int id) {
		this.id = id;
	}
}
/*測試類*/
public class PrototypeTest {
    public static void main(String[] args) {  
    	ConcretePrototype prototype = new ConcretePrototype();
        prototype.setName("exp");
        prototype.setId(1);
        ConcretePrototype prototype2 = (ConcretePrototype) prototype.clone();
    	System.out.println("prototype is equals prototype2?"+prototype.equals(prototype2));
    	System.out.println("prototype2.getName():"+prototype2.getName()+",prototype2.getId():"+prototype2.getId());
    }
}

  結果示例:

prototype is equals prototype2?false
prototype2.getName():exp,prototype2.getId():1

5.5 原型模式應用場景

  原型模式通常適用的場景:
   1)對象之間相同或相似,常見的情況是:只有幾個屬性不同。
   2)對象的創建過程比較麻煩,但複製比較簡單的時候。
  參考資料:創建型模式的特點和分類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章