5 回答

TA貢獻1796條經驗 獲得超4個贊
在談論單例時,我經常試圖向人們解釋一個重要的觀點:
單例與僅創建 1 個實例的事物之間存在差異。而且,通常,當您認為自己想要一個單例時,實際上您只是想要只創建 1 個實例的東西。
這兩件事之間的區別一開始可能并不明顯,但認識到這一點很重要,特別是當您發現自己需要在測試之間清除單例的內部狀態時。
如果您有一個單例(真正的單例),根據定義,JVM 中可以存在一個實例。如果它具有可變狀態,那么這是有問題的,因為這意味著您必須關心該狀態。在測試中,您必須清除運行之間的狀態,以消除由于測試執行順序而產生的任何影響;并且您必須連續運行測試。
如果您使用依賴注入(如概念中所示,而不是像 Guice、Dagger、Spring 等任何特定框架),那么使用該實例來自的實例的類并不重要:您作為該類的客戶端,得到控制其生命周期。因此,雖然您的生產代碼在所有地方使用相同的實例,但您的測試代碼可以使用單獨的實例 - 因此它們是解耦的- 通常您甚至根本不必擔心清理狀態,因為您的下一個測試用例可以只需創建該類的一個新實例即可。
因此,不要像Core
這樣使用類的代碼:
class MyClass {
? void foo() {
? ? Core core = Core.getInstance();
? ? // ... do stuff with the Core instance.
? }
}
你可以這樣寫:
class MyClass {
? private final Core core;
? MyClass(Core core) { this.core = core; }
? void foo() {
? ? // ... do stuff with the Core instance.
? }
}
MyClass并且您已經破壞了和之間的靜態綁定Core。您可以MyClass在測試中實例化以下單獨的實例Core:
MyClass myClass = new MyClass(new Core());
// Assert something...
或者,如果多個實例需要與同一實例交互Core:
Core core = new Core();
MyClass myClass = new MyClass(core);
MyOtherClass myOtherClass = new MyOtherClass(core);
// Assert something...

TA貢獻1828條經驗 獲得超4個贊
您應該創建構造函數private,以便使用單例類的代碼無法使用它創建實例,并且它們應該只能使用該方法獲取實例getInstance()。
此外,單例對象的生命周期通常與 JVM 相關,因為每個 JVM 都應該有一個單例類的實例。因此,如果您可以銷毀并重新創建實例,那么它就不是真正的單例(IMO),所以我假設您只想重新創建實例以進行測試。
要在調用該方法后從測試類重新創建單例,destroy()您可以獲取Field具有您的類實例的類的 。使用它,Field您可以將其設置為您創建的新實例:
public static void main(String[] args) throws Exception {
System.out.println(Core.getInstance()); //gets instance
Core.destroy();
System.out.println(Core.getInstance()); // null
reinitializeInstance(Core.class);
System.out.println(Core.getInstance()); //gets instance
}
public static void reinitializeInstance(Class<Core> clazz) {
try {
Constructor<Core> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Core newCore = constructor.newInstance();
Field field = Core.class.getDeclaredField("instance"); //gets the instance field
field.setAccessible(true);
field.set(newCore, newCore);
} catch (Exception e) {
e.printStackTrace();
}
}
還有你的 Singleton 類:
class Core {
private static Core instance = new Core();
// To prevent reflection from creating a new instance without destroying the first one
private Core() {
if(instance != null){
throw new IllegalStateException("Instance already exists!");
}
}
public static Core getInstance() {
return instance;
}
public static void destroy() {
instance = null;
}
}

TA貢獻1780條經驗 獲得超1個贊
public class Core {
private static class SingletonHolder {
private static AtomicReference<Core> instance = new AtomicReference(new Core());
}
public static Core getInstance() {
return SingletonHolder.instance.get();
}
public static void destroy() {
SingletonHolder.instance.set(null);
}
public static void reset() {
SingletonHolder.instance.compareAndSet(null, new Core());
}
}
使用額外的“多余”內部類來進行并發初始化,確保靜態字段被初始化一次。
更改實例(銷毀、重置)需要對對象進行某種同步。synchronize可以使用 AtomicReference來代替成本更高的方法。
compareAndSet如果已有舊值,則不會為實例設置新值。
也是值得擁有的
Optional<Core> getInstance() { ... }
所以使用是有保障的。
Core.getInstance().ifPresent(core -> { ... core ... });

TA貢獻1809條經驗 獲得超8個贊
首先,您要使 Core 構造函數可訪問,但默認情況下它已經是公共的。
其次,當你調用構造函數時,它只是創建一個新的 Core 實例,它對實例沒有任何作用,因為默認創建的構造函數是空的,而且構造函數不是初始化 Singleton 的地方。
如果你想刷新單例實例,你應該有一個專用的方法。

TA貢獻1876條經驗 獲得超7個贊
換成這個模式怎么樣?
public class Core{
private static Core instance;
public static Core getInstance() {
if(instance == null) instance = new Core();
return instance;
}
public static void destroy(){
instance = null;
}
}
如果您只想在測試中銷毀,您可以從 destroy() 方法中刪除“public”
添加回答
舉報