【Java】設計模式--建立型模式

TypantK發表於2019-03-21

建立型模式


目錄

工廠模式

普通的工廠(引數是字串)

☆多個方法工廠(沒有字串引數,呼叫不同方法作為傳遞引數方式 --> 工廠多個方法)

☆☆靜態工廠方法模式(最優解BEST)

抽象工廠模式

單例模式

一般單例模式

☆☆列舉型別(最佳實現單例)

建造者模式

Builder:套餐規範

ConcurrentBuilder:每個套餐應該說明(實現)套餐的每個內容是什麼

Director:根據套餐選單,返回出對應套餐的食物

Product:最後得到的食物

測試:拿到選單A(mealA)後點餐,得到meal,不用知道是怎麼組成這個物件meal的


 

工廠模式

*組成:

  • 產品規範:一個介面(抽象類)
  • 產品:實現此介面(繼承抽象類)的類
  • 工廠:根據“引數”返回不同實現(繼承)類的類

*流程:

使用者需要某個產品(實現類),使用對應“引數” --> 工廠 --> 工廠根據“引數”產生不同的物件

*好處:

只需要傳遞“引數”給工廠,就可以得到某種類,不需要自己去實現

*缺點:

如果要增加產品(實現類),也就是工廠要返回的物件種類變多,就要修改工廠,違反了開閉原則(對擴充套件開放/修改關閉)

 

普通的工廠(引數是字串)

//規範實現類的介面
interface Car{
	public abstract void run();
	
	public abstract void stop();
}

//實現類A(使用者得到的產品)
class Benz implements Car{

	@Override
	public void run() {
		System.out.println("Benz的run()方法");
		
	}

	@Override
	public void stop() {
		System.out.println("Benz的stop()方法");
	}
	
}

//實現類B(使用者得到的產品)
class BMW implements Car{

	@Override
	public void run() {
		System.out.println("BMW的run()方法");
		
	}

	@Override
	public void stop() {
		System.out.println("BMW的stop()方法");
	}
	
}

//工廠類
class Factory{
	public  Car getCarInstance(String car) {
		Car c = null;
		if("Benz".equals(car)) {
			c = new Benz();
		}else if("BMW".equals(car)) {
			c = new BMW();
		}
		return c;
	}
}

//測試 是否工廠能夠正確的生產產品(實現類)
public class FactoryPattern {
	
	public static void main(String[] args) {
		Factory f = new Factory();
		Car c = f.getCarInstance("Benz");
		c.run();
		c.stop();
	}
	
}

 

☆多個方法工廠(沒有字串引數,呼叫不同方法作為傳遞引數方式 --> 工廠多個方法)

*好處:

不用再傻乎乎的傳引數給工廠了,因為引數可能傳錯,呼叫方法的話編譯階段就可以知道錯誤了。

對於上面的普通工廠,改動工廠類即可

class FactoryB{
	//多個方法工廠
	public Car getBenz() {
		return new Benz();
	}
	
	public Car getBMW() {
		return new BMW();
	}
}
public class FactoryPattern {
	
	public static void main(String[] args) {
		FactoryB factory = new FactoryB();
		Car c = factory.getBMW();
		c.run();
		c.stop();
	}
	
}

 

☆☆靜態工廠方法模式(最優解BEST)

將多個方法工廠裡面方法設定成靜態

*好處:

想了想,工廠好像除了呼叫方法返回物件之後就沒什麼用了,為什麼不用靜態方法呢,這樣就少建立了一個工廠例項咯(#^.^#)

class FactoryC{
	//靜態工廠
	public static Car getBenz() {
		return new Benz();
	}
	
	public static Car getBMW() {
		return new BMW();
	}
}

public class FactoryPattern {
	
	public static void main(String[] args) {
		Car c = FactoryC.getBenz();
		c.run();
		c.stop();
	}
	
}

 

*總而言之,靜態工廠方法模式是最優最優的,因為沒有工廠例項(節省空間了),而且不用傳字串引數,減少出錯

 

 

抽象工廠模式

為了解決工廠模式擴充業務(工廠產生新種類物件)時要修改工廠的問題,

使用抽象工廠模式,一旦需要增加新的種類的實現類,直接增加新的工廠,不需要修改之前的程式碼

 

*組成:

  • 產品規範:一個介面(抽象類)
  • 產品:實現此介面(繼承抽象類)的類
  • 工廠:一個工廠對應一個物件,直接返回這個工廠唯一的產品(物件)
  • 抽象工廠:實現此介面的工廠才可以返回其工廠的產品給使用者,管理所有的工廠

*流程:

使用者傳遞工廠名 --> 抽象工廠根據工廠名找到工廠 --> 不同的工廠直接返回不同的物件(產品)

*好處:

一旦需要增加新的種類(新的產品),直接增加新的工廠就好,不用修改工廠程式碼。

//產品規範
interface FillColor{
	public void Fill();
}

//具體產品A(實現類)
class Pink implements FillColor{
	@Override
	public void Fill() {
		// TODO Auto-generated method stub
		System.out.println("Filling Pink");
	}
}

//具體產品B(實現類)
class Blue implements FillColor{
	@Override
	public void Fill() {
		// TODO Auto-generated method stub
		System.out.println("Filling Blue");
	}
}

//工廠的規範,工廠呼叫這個方法就產出產品
abstract class AbstractFactory{
	public abstract FillColor ProvideProduct();
}

//工廠A以及產出A產品
class FactoryPink extends AbstractFactory{
	@Override
	public FillColor ProvideProduct() {
		// TODO Auto-generated method stub
		return new Pink();
	}
}

//工廠B以及產出B產品
class FactoryBlue extends AbstractFactory{
	@Override
	public FillColor ProvideProduct() {
		// TODO Auto-generated method stub
		return new Blue();
	}
}

public class AbstractFactoryPattern {
	
	public static void main(String[] args) {
		AbstractFactory factory = new FactoryPink();
		FillColor pink = factory.ProvideProduct();
		pink.Fill();
	}
	
}

 

單例模式

*好處:

  • 某些類的建立比較頻繁(各種成員屬性),並且對於大型物件建立很麻煩,就可以使用單例
  •  
  • 作為通訊的媒介,資料共享,可以在不建立直接關聯的條件下,讓多個不相關的執行緒或者多個程式之間進行通訊

*缺點:

  • 沒有抽象層,擴充套件困難,需要擴充套件的話要修改原始碼
  • 職責過重,既要負責工廠方法又要提供業務方法,違背單一職責原則
  • 例項化的物件長時間不被利用可能被GC

 

static Instance原因

  • 要被靜態方法getInstance()呼叫
  • static變數儲存在方法區,每次都指向同一個變數

 

一般單例模式

懶漢式

public class Singleton {
	//賦值為null,實現延遲載入
	private static Singleton instance = null;
	
    //私有構造方法 防止被例項化(直接new)
	private Singleton() {
		
	}
	
	private static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	
}

不用想,肯定有多執行緒問題,所以一般想到的就是改進①:synchronized關鍵字

	private static synchronized Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}

這樣每次呼叫此方法時鎖住的是整個物件,效能下降,但是我們只需要在第一次建立物件時才鎖住物件

改進②雙檢鎖:

	private static Singleton getInstance() {
		if(instance == null) {
			synchronized(instance) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}

好像解決了問題,但是instance = new Singleton()這一句不是原子操作,分為三步

  • 1:給instance分配記憶體
  • 2:呼叫Singleton建構函式初始化成員變數
  • 3:將instance物件指向分配的記憶體空間(此時instance ≠ null)

由於JVM的指令重排序優化機制,所以最終執行時是1-3-2。

如果A程式執行完3就切換程式了,程式B執行時發現instance不為空,呼叫時發現還未初始化就報錯了

所以要禁止重排序,很容易就想到可以用volatile修飾instance達到效果(也就是寫操作A優先於讀操作B)

但是特別注意在 Java 5 以前的版本使用了 volatile 的雙檢鎖還是有問題的。其原因是 Java 5 以前的 JMM (Java 記憶體模型)是存在缺陷的,即時將變數宣告成 volatile 也不能完全避免重排序,主要是 volatile 變數前後的程式碼仍然存在重排序問題。這個 volatile 遮蔽重排序的問題在 Java 5 中才得以修復,所以在這之後才可以放心使用 volatile。
--------------------- 
作者:一往無前-千夜 
來源:CSDN 
原文:https://blog.csdn.net/wolfking0608/article/details/69066773 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

 

改進③final:餓漢式,第一次載入類到記憶體就會被初始化

*優點:執行緒安全,呼叫效率高(沒有鎖)

*缺點:不能延時載入,空間換時間,浪費記憶體

public class Singleton{
    //類載入時就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}

沒有延遲載入的結果 --> 某些情況使用不了:譬如 Singleton 例項的建立是依賴引數或者配置檔案的,在 getInstance() 之前必須呼叫某個方法設定引數給它,那樣這種單例寫法就無法使用了

 

☆☆改進④靜態內部類實現:延遲載入

*靜態內部類:呼叫時(執行getInstance())才會載入,外部類載入了也不會載入

public class Singleton {
	 
	private Singleton() {
	}
 
	/* 此處使用一個內部類來維護單例 */
	private static class SingletonFactory {
		private static final Singleton instance = new Singleton();
	}
 
	/* 獲取例項 */
	public static Singleton getInstance() {
		return SingletonFactory.instance;
	}

}

 

改進④:建立與獲取分離

靜態內部類的另外一種實現,將獲取單例與建立單例分成兩個函式

public class SingletonTest {
 
	private static SingletonTest instance = null;
 
	private SingletonTest() {
	}
 
	private static synchronized void syncInit() {
		if (instance == null) {
			instance = new SingletonTest();
		}
	}
 
	public static SingletonTest getInstance() {
		if (instance == null) {
			syncInit();
		}
		return instance;
	}
}

 

☆☆列舉型別(最佳實現單例)

*優點:簡單,不用像上面考慮過多多執行緒問題

*缺點:沒有使用延遲載入,在靜態程式碼塊初始化了

實現單例的核心是構造方法私有化,而在列舉型別中構造方法本身就是私有的,且列舉型別是執行緒安全的

public enum EnumSingleton{
    INSTANCE;
}

直接通過EnumSingleton.INSTANCE就可以訪問單例,VERY EASY.

 

**列舉型別的實現:反編譯class檔案

P.S. jd-gui會直接反編譯成enum

package com.create;


public final class Color extends Enum
{

    public static Color[] values()
    {
        return (Color[])$VALUES.clone();
    }

    public static Color valueOf(String s)
    {
        return (Color)Enum.valueOf(com/create/Color, s);
    }

    private Color(String s, int i)
    {
        super(s, i);
    }

    public static final Color INSTANCE;
    private static final Color $VALUES[];

    static 
    {
        INSTANCE = new Color("INSTANCE", 0);
        $VALUES = (new Color[] {
            INSTANCE
        });
    }
}

可以看到,類似於餓漢式使用static 和 final修飾唯一物件INSTANCE

以及在靜態程式碼塊中初始化(Java類的載入和初始化過程都是執行緒安全的,所以INSTANCE也是確保了執行緒安全

所以使用enum列舉實現單例模式不能延遲載入

 

 

建造者模式

相比於工廠模式,工廠模式是提供單個類的不同實現方式(飲料的實現:可樂/雪碧),而建造者模式是將這些不同的類再組合在一個類(飲料+食物)類似於套餐

*流程:

根據已有的套餐規範(Builder)已建立套餐選單A/B -->使用者拿著套餐選單A(ConcreteBuilder

---> 找服務員Waiter(Director)根據此創造對應套餐Meal(Product

 

*好處:

只需要拿到套餐選單(ConcurrentBuilder)給Waiter(Director),就可以得到組合的物件

 

Builder:套餐規範

 

//抽象建造者Builder
abstract class MealBuilder{
	Meal meal = new Meal();
	
	public abstract void buildFood();
	
	public abstract void buildDrink();
	
	public Meal getMeal() {
		return meal;
	}
}

ConcurrentBuilder:每個套餐應該說明(實現)套餐的每個內容是什麼

//具體建造者A ConcreteBuilder
class MealA extends MealBuilder{

	@Override
	public void buildFood() {
		// TODO Auto-generated method stub
		meal.setFood("漢堡");
	}
	
	@Override
	public void buildDrink() {
		// TODO Auto-generated method stub
		meal.setDrink("可樂");
	}
}
//具體建造者B ConcreteBuilder
class MealB extends MealBuilder{

	@Override
	public void buildFood() {
		// TODO Auto-generated method stub
		meal.setFood("蘭州拉麵");
	}
	
	@Override
	public void buildDrink() {
		// TODO Auto-generated method stub
		
	}
}

Director:根據套餐選單,返回出對應套餐的食物

//Director
class Waiter {
	private MealBuilder mealBuilder;
	
	public Waiter(MealBuilder mealBuilder) {
		this.mealBuilder = mealBuilder;
	}
	
	public Meal construct() {
		mealBuilder.buildFood();
		mealBuilder.buildDrink();
		return mealBuilder.getMeal();
	}
	
}

Product:最後得到的食物

//Product
class Meal{
	
	private String food;
	private String drink;
	
	public String getFood() {
		return food;
	}
	public void setFood(String food) {
		this.food = food;
	}
	public String getDrink() {
		return drink;
	}
	public void setDrink(String drink) {
		this.drink = drink;
	}
}

測試:拿到選單A(mealA)後點餐,得到meal,不用知道是怎麼組成這個物件meal的

public class Builder {
	public static void main(String[] args) {
		MealA mealA = new MealA();
		Waiter w = new Waiter(mealA);
		Meal m = w.construct();
		System.out.println("食物:" + m.getFood() + " 飲料:" + m.getDrink());
		
	}
}

 

原型模式

首先建立一個原型物件,通過對原型物件的複製,產生出更多同型別的物件(淺拷貝/深拷貝)

https://blog.csdn.net/TypantK/article/details/88708343 -- 3種淺拷貝/2種深拷貝

 

 

相關文章