目錄
- 單例模式
- 單例模式結構
- 單例模式適用場景
- 單例模式優缺點
- 練手題目
- 題目描述
- 輸入描述
- 輸出描述
- 輸入示例
- 輸出示例
- 提示信息
- 題解
單例模式
單例模式是一種創建型設計模式, 讓你能夠保證一個類只有一個實例, 并提供一個訪問該實例的全局節點。
-
只有一個實例的意思是,在整個應用程序中,只存在該類的一個實例對象,而不是創建多個相同類型的對象。
-
全局訪問點的意思是,為了讓其他類能夠獲取到這個唯一實例,該類提供了一個全局訪問點(通常是一個靜態方法),通過這個方法就能獲得實例。
單例模式結構
-
私有的構造函數:防止外部代碼直接創建類的實例
-
私有的靜態實例變量:保存該類的唯一實例
-
公有的靜態方法:通過公有的靜態方法來獲取類的實例
單例模式通用代碼
單例模式的實現方式有多種,包括懶漢式、餓漢式等。
-
餓漢式指的是在類加載時就已經完成了實例的創建,不管后面創建的實例有沒有使用,先創建再說,所以叫做 “餓漢”。
-
而懶漢式指的是只有在請求實例時才會創建,如果在首次請求時還沒有創建,就創建一個新的實例,如果已經創建,就返回已有的實例,意思就是需要使用了再創建,所以稱為“懶漢”。
餓漢模式
//單例類通過'getSingleton(獲取實例)'方法進行定義,讓客戶端全局獲得該對象實例
public class Singleton{// 保存單例實例成員變量必須被聲明為靜態類型
private static final Singleton singleton=new Singleton();// 單例的構造函數必須永遠是私有類型,以防止使用`new`運算符直接調用構造方法。限制產生多個對象
private Singleton(){}//通過該方法獲得實例對象
public static Singleton getSingleton(){
return singleton;}
//類中其他方法,盡量是static
public static void doSomething(){}
}
懶漢模式
public class Singleton {private static Singleton instance;private Singleton() {// 私有構造方法,防止外部實例化}// 使用了同步關鍵字來確保線程安全, 可能會影響性能public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
在懶漢模式的基礎上,可以使用雙重檢查鎖來提高性能。
public class Singleton {private static volatile Singleton instance;private Singleton() {// 私有構造方法,防止外部實例化}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
有上限的多例模式,它是單例模式的一種擴展,采用有上限的多例模式,我們可以在設計時決定在內存中有多少個實例,方便系統進行擴展,修正單例可能存在的性能問題,提供系統的響應速度。
public class Singleton{private static int maxNum=3;//最多產生3個實例數量private static ArrayList<Singleton> List=new ArrayList<Singleton>();private static int count=0;//當前皇帝序列號static{for(int i=0;i<maxNum;i++){List.add(new Singleton());}}private Singleton(){}private static Singleton getInstance(){Random random=new Random();count=random.nextInt(maxNum);return List.get(count);}}
單例模式適用場景
-
如果程序中的某個類對于所有客戶端只有一個可用的實例, 可以使用單例模式。
單例模式禁止通過除特殊構建方法以外的任何方式來創建自身類的對象。 該方法可以創建一個新對象, 但如果該對象已經被創建, 則返回已有的對象。
-
如果你需要更加嚴格地控制全局變量, 可以使用單例模式。
單例模式與全局變量不同, 它保證類只存在一個實例。 除了單例類自己以外, 無法通過任何方式替換緩存的實例。
識別方法: 單例可以通過返回相同緩存對象的靜態構建方法來識別。
單例模式優缺點
單例模式優點:
-
你可以保證一個類只有一個實例。
-
你獲得了一個指向該實例的全局訪問節點。
-
僅在首次請求單例對象時對其進行初始化。
單例模式缺點:
-
違反了單一職責原則。 該模式同時解決了兩個問題。
-
單例模式一般沒有接口,擴展很困難,若要擴展,除了修改代碼基本上沒有第二種途徑可以實現。
-
單例模式可能掩蓋不良設計, 比如程序各組件之間相互了解過多等。
-
該模式在多線程環境下需要進行特殊處理, 避免多個線程多次創建單例對象。
-
單例的客戶端代碼單元測試可能會比較困難, 因為許多測試框架以基于繼承的方式創建模擬對象。 由于單例類的構造函數是私有的, 而且絕大部分語言無法重寫靜態方法, 所以你需要想出仔細考慮模擬單例的方法。 要么干脆不編寫測試代碼, 或者不使用單例模式。
練手題目
題目描述
小明去了一家大型商場,拿到了一個購物車,并開始購物。請你設計一個購物車管理器,記錄商品添加到購物車的信息(商品名稱和購買數量),并在購買結束后打印出商品清單。(在整個購物過程中,小明只有一個購物車實例存在)。
輸入描述
輸入包含若干行,每行包含兩部分信息,分別是商品名稱和購買數量。商品名稱和購買數量之間用空格隔開。
輸出描述
輸出包含小明購物車中的所有商品及其購買數量。每行輸出一種商品的信息,格式為 “商品名稱 購買數量”。
輸入示例
Apple 3 Banana 2 Orange 5
輸出示例
Apple 3 Banana 2 Orange 5
提示信息
本道題目請使用單例設計模式, 使用私有靜態變量來保存購物車實例。 使用私有構造函數防止外部直接實例化。
題解
1.簡單單例模式實現。
import java.util.Scanner;
import java.util.ArrayList;class ShoppingCart {private static ShoppingCart instance = new ShoppingCart();private static ArrayList<String> productNames = new ArrayList<>();private static ArrayList<Integer> productQuantities = new ArrayList<>();private ShoppingCart() {}public static ShoppingCart getInstance() {return instance;}public void Add(String name, int quantity) {productNames.add(name);productQuantities.add(quantity);System.out.println(name + " " + quantity);}
}public class Main {public static void main(String[] args) {ShoppingCart cart = ShoppingCart.getInstance();Scanner scanner = new Scanner(System.in);String inputLine;while (scanner.hasNextLine()) {inputLine = scanner.nextLine();if ("exit".equalsIgnoreCase(inputLine)) {break;}String[] parts = inputLine.split(" ");if (parts.length == 2) {String name = parts[0];int quantity;try {quantity = Integer.parseInt(parts[1]);cart.Add(name, quantity);} catch (NumberFormatException e) {System.out.println("輸入錯誤,請重新輸入");}} else {System.out.println("輸入錯誤,請重新輸入");}}scanner.close();}
}
2.在懶漢模式的基礎上,可以使用雙重檢查鎖來提高性能。
import java.util.Scanner;
import java.util.ArrayList; class ShoppingCart {// 購物車類的單例實例變量,使用volatile關鍵字確保線程安全private static volatile ShoppingCart instance;// 存儲商品名稱private static ArrayList<String> productNames = new ArrayList<>();// 存儲商品數量private static ArrayList<Integer> productQuantities = new ArrayList<>();// 私有構造函數,防止外部直接創建ShoppingCart對象private ShoppingCart() {}// 獲取購物車單例實例的方法,確保線程安全public static ShoppingCart getInstance() {if (instance == null) {synchronized (ShoppingCart.class) {if (instance == null) {instance = new ShoppingCart();}}}return instance;}// 添加商品到購物車的方法public void Add(String name, int quantity) {productNames.add(name); productQuantities.add(quantity); System.out.println(name + " " + quantity);}
}public class Main {public static void main(String[] args) {ShoppingCart cart = ShoppingCart.getInstance(); Scanner scanner = new Scanner(System.in); String inputLine;// 循環讀取用戶輸入,直到用戶輸入"exit"while (scanner.hasNextLine()) {inputLine = scanner.nextLine();if ("exit".equalsIgnoreCase(inputLine)) {break;}// 使用空格分割輸入的字符串,獲取商品名稱和數量String[] parts = inputLine.split(" ");// 確保輸入格式正確,即包含兩個部分:商品名稱和數量if (parts.length == 2) {// 商品名稱String name = parts[0]; // 商品數量int quantity; try {// 將第二部分轉換為整數quantity = Integer.parseInt(parts[1]);cart.Add(name, quantity); } catch (NumberFormatException e) {// 如果轉換失敗,輸出錯誤信息System.out.println("轉換失敗,請重新輸入");}} else {// 如果輸入格式不正確,輸出錯誤信息System.out.println("如果輸入格式不正確,請重新輸入");}}scanner.close();}
}