溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何在Unity3D中使用單例模式和靜態類

發布時間:2021-04-13 17:47:40 來源:億速云 閱讀:513 作者:Leah 欄目:開發技術

本篇文章為大家展示了如何在Unity3D中使用單例模式和靜態類,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

1、靜態類不能繼承和被繼承?。▏栏顸c說是只能繼承System.Object)也就是說你的靜態類不可能去繼承MonoBehaviour,不能實現接口。

2、靜態方法不能使用非靜態成員!如果你大量使用靜態方法,而方法里又需要用到這個類的成員,那么你的成員得是靜態成員。

第2點需要注意:如果你想在Unity的編輯器下調整某個參數,那么這個參數就不能是靜態的(哪怕你自定義EditorWindow去修改這個值也沒用),解決的辦法是通過UnityEngine.ScriptableObject去存放配置(生成*.asset文件),然后在運行中通過LoadAsset去加載,然后再改變靜態成員。至于原因,相信不難理解——你看到的所有Unity組件都是一個個實例,你要通過Unity的編輯器去配置,那么你就得有一個這樣的可配置實例。

從面向對象上想一下:靜態方法或者靜態類,不需要依賴對象,類是唯一的;單例的靜態實例,一般就是唯一的一個對象(當然也可以有多個)。差別嘛。。。好像也不大。。。

如果這樣考慮沒有錯,那再回頭比較一下兩種方式:

1、靜態(靜態方法或者靜態類),代碼編寫上絆手絆腳,方法調用很方便,運行效率高一丟丟。邏輯面向過程,不能很好地控制加載和銷毀。

2、單例(類的靜態實例),代碼編寫和其他類完全一樣,繼承抽象模版接口都可以,Unity里也很方便進行參數配置,不過使用麻煩有犯錯的可能性(必須通過實例調用方法),效率不如靜態(但是也不會有很大影響吧)。

如果這些說法太抽象,那我再給出一個常見的問題:如果你的框架有一個SoundManager能夠管理所有的聲音播放,那么你會怎么去實現?

(在剛接觸AudioSource這個組件的時候,我想的是每一個聲音都由一個AudioSource去播放。但是后來發現完全沒必要,AudioSource有靜態的PlayClipAtPoint方法去播放臨時3D音效,同時有實例方法PlayOneShot去播放臨時音效(2D和3D取決于當實例的SpatialBlend)。如果沒有特殊的需求,那么一個AudioSource循環播放背景音樂,上述兩種方法播放游戲中的特效音頻,這對于大部分游戲已經足夠了。)

那么問題來了:你的SoundManager播放聲音的方法如果是靜態的,那么AudioSource組件必須在代碼中通過各種方式去獲?。ㄐ陆ńM件或者獲取特定GameObject下的組件)——因為保存這個組件的變量必須是靜態的,也就不能通過Unity的編輯器去賦值。如果不去閱讀代碼那么用戶完全不知道這是一個什么樣的組件獲取流程,如果我破壞這個流程(同名物體,包含互斥組件等),那么這個Manager很有可能會出現不可預料的異常。

而繼承MonoBehaviour并RequireComponent(typeof(AudioSource)),怎么看也比“為了靜態而靜態”的代碼要方便健壯的多。

實際上到這里已經可以基本總結出何時需要使用單例了:

1、只要你的類需要保存其他組件作為變量,那么就有必要使用單例;

2、只要你有在Unity編輯器上進行參數配置的需求,那么就有必要使用單例;

3、只要你的管理器需要進行加載的順序控制,那么就有必要使用單例(比如熱更新之后加載ResourcesManager);

當然,這里都只是“有必要”,并不是“必須”。兩者區別最大的地方,一個是方便寫,一個是方便用。方便寫的代價是每次調用加個instance,方便用的代價則是放棄了面向對象和Unity的“所見即所得”,孰輕孰重,自己抉擇。

另一方面,和“為了靜態而靜態”一樣,“為了單例而單例”同樣是一個不合理的設計。這樣的解釋仍然是那么的模糊,那么,就給自己定義一個最簡單的規則吧——如果你的單例類里沒有任何需要保存狀態的變量,那么這個類里的方法就可以全都是靜態方法,這個類也可以是個靜態類。

補充:從實例出發,了解單例模式和靜態塊

就算你沒有用到過其他的設計模式,但是單例模式你肯定接觸過,比如,Spring 中 bean 默認就是單例模式的,所有用到這個 bean 的實例其實都是同一個。

單例模式的使用場景

什么是單例模式呢,單例模式(Singleton)又叫單態模式,它出現目的是為了保證一個類在系統中只有一個實例,并提供一個訪問它的全局訪問點。從這點可以看出,單例模式的出現是為了可以保證系統中一個類只有一個實例而且該實例又易于外界訪問,從而方便對實例個數的控制并節約系統資源而出現的解決方案。

使用單例模式當然是有原因,有好處的了。在下面幾個場景中適合使用單例模式:

1、有頻繁實例化然后銷毀的情況,也就是頻繁的 new 對象,可以考慮單例模式;

2、創建對象時耗時過多或者耗資源過多,但又經常用到的對象;

3、頻繁訪問 IO 資源的對象,例如數據庫連接池或訪問本地文件;

下面舉幾個例子來說明一下:

1、網站在線人數統計;

其實就是全局計數器,也就是說所有用戶在相同的時刻獲取到的在線人數數量都是一致的。要實現這個需求,計數器就要全局唯一,也就正好可以用單例模式來實現。當然這里不包括分布式場景,因為計數是存在內存中的,并且還要保證線程安全。下面代碼是一個簡單的計數器實現。

public class Counter {   
    private static class CounterHolder{
        private static final Counter counter = new Counter();
    }
    private Counter(){
        System.out.println("init...");
    }
    public static final Counter getInstance(){
        return CounterHolder.counter;
    }
    private AtomicLong online = new AtomicLong();
    public long getOnline(){
        return online.get();
    }
    public long add(){
        return online.incrementAndGet();
    }
}

2、配置文件訪問類;

項目中經常需要一些環境相關的配置文件,比如短信通知相關的、郵件相關的。比如 properties 文件,這里就以讀取一個properties 文件配置為例,如果你使用的 Spring ,可以用 @PropertySource 注解實現,默認就是單例模式。如果不用單例的話,每次都要 new 對象,每次都要重新讀一遍配置文件,很影響性能,如果用單例模式,則只需要讀取一遍就好了。以下是文件訪問單例類簡單實現:

public class SingleProperty {
    private static Properties prop;
    private static class SinglePropertyHolder{
        private static final SingleProperty singleProperty = new SingleProperty();
    }
    /**
    * config.properties 內容是 test.name=kite 
    */
    private SingleProperty(){
        System.out.println("構造函數執行");
        prop = new Properties();
        InputStream stream = SingleProperty.class.getClassLoader()
                .getResourceAsStream("config.properties");
        try {
            prop.load(new InputStreamReader(stream, "utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SingleProperty getInstance(){
        return SinglePropertyHolder.singleProperty;
    }    
 
    public String getName(){
        return prop.get("test.name").toString();
    }
    public static void main(String[] args){
        SingleProperty singleProperty = SingleProperty.getInstance();
        System.out.println(singleProperty.getName());
    }
}

3、數據庫連接池的實現,也包括線程池。

為什么要做池化,是因為新建連接很耗時,如果每次新任務來了,都新建連接,那對性能的影響實在太大。所以一般的做法是在一個應用內維護一個連接池,這樣當任務進來時,如果有空閑連接,可以直接拿來用,省去了初始化的開銷。

所以用單例模式,正好可以實現一個應用內只有一個線程池的存在,所有需要連接的任務,都要從這個連接池來獲取連接。

如果不使用單例,那么應用內就會出現多個連接池,那也就沒什么意義了。如果你使用 Spring 的話,并集成了例如 druid 或者 c3p0 ,這些成熟開源的數據庫連接池,一般也都是默認以單例模式實現的。

單例模式的實現方法

如果你在書上或者網站上搜索單例模式的實現,一般都會介紹5、6中方式,其中有一些隨著 Java 版本的升高,以及多線程技術的使用變得不那么實用了,這里就介紹兩種即高效,而且又是線程安全的方式。

1. 靜態內部類方式

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

這種寫法仍然使用 JVM 本身機制保證了線程安全問題,由于 SingletonHolder 是私有的,除了 getInstance() 方法外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。上面的兩個例子就是用這種方式實現的。

2. 枚舉方式

public enum SingleEnum {
    INSTANCE;
    SingleEnum(){
        System.out.println("構造函數執行");
    }
    public String getName(){
        return "singleEnum";
    }
    public static void main(String[] args){
        SingleEnum singleEnum = SingleEnum.INSTANCE;
        System.out.println(singleEnum.getName());
    }
}

我們可以通過 SingleEnum.INSTANCE 來訪問實例。而且創建枚舉默認就是線程安全的,并且還能防止反序列化導致重新創建新的對象。

靜態塊

什么是靜態塊呢

1、它是隨著類的加載而執行,只執行一次,并優先于主函數。具體說,靜態代碼塊是由類調用的。類調用時,先執行靜態代碼塊,然后才執行主函數的;

2、靜態代碼塊其實就是給類初始化的,而構造代碼塊是給對象初始化的;

3、靜態代碼塊中的變量是局部變量,與普通函數中的局部變量性質沒有區別;

4、一個類中可以有多個靜態代碼塊;

他的寫法是這樣的:

static {
        System.out.println("static executed");
    }

來看一下下面這個完整的實例:

public class SingleStatic {
    static {
        System.out.println("static 塊執行中...");
    }
    {
        System.out.println("構造代碼塊 執行中...");
    }
    public SingleStatic(){
        System.out.println("構造函數 執行中");
    }
    public static void main(String[] args){
        System.out.println("main 函數執行中");
        SingleStatic singleStatic = new SingleStatic();
    }
}

他的執行結果是這樣的:

static 塊執行中...

main 函數執行中

構造代碼塊 執行中...

構造函數 執行中

從中可以看出他們的執行順序分別為:

1、靜態代碼塊

2、main 函數

3、構造代碼塊

4、構造函數

利用靜態代碼塊只在類加載的時候執行,并且只執行一次這個特性,也可以用來實現單例模式,但是不是懶加載,也就是說每次類加載就會主動觸發實例化。

上述內容就是如何在Unity3D中使用單例模式和靜態類,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女