本文向您展示如何在 Unity3D 中使用单例模式和静态类。 内容简洁易懂。 绝对会让你眼前一亮。 希望您能从本文的详细介绍中有所收获。
1.静态类不能继承和被继承! (严格来说,它只能继承System.Object)也就是说,你的静态类不能继承MonoBehaviour,也不能实现该接口。
2.静态方法不能使用非静态成员! 如果你使用了很多静态方法贴图笔刷,并且你需要在方法中使用这个类的成员,那么你的成员必须是静态成员。
第2点需要注意的是:如果你想调整Unity的编辑器中的某个参数,那么这个参数不能是静态的(即使你自定义EditorWindow来修改这个值也是没有用的)。 解决方案是使用UnityEngine。 ScriptableObject存储配置(生成*.asset文件),然后运行时通过LoadAsset加载,然后更改静态成员。 至于原因,我相信也不难理解——你看到的所有Unity组件都是实例,你必须通过Unity编辑器来配置它们,所以你必须有这样一个可配置的实例。
从面向对象的角度思考:静态方法或静态类不需要依赖对象,类是唯一的; 单例的静态实例一般是唯一的对象(当然可以有多个)。 区别。 。 。 看起来也不是很大。 。 。
如果这样想并没有什么问题的话,那么回过头来对比一下两种方法:
1.静态(静态方法或静态类),代码写起来比较困难,方法调用很方便,运行效率高一点。 该逻辑是面向过程的,无法提供对加载和销毁的良好控制。
2、单例(类的静态实例),代码编写和其他类一模一样,可以继承抽象模板接口,而且Unity中参数配置也很方便unity中多线程,但是使用起来比较麻烦,有可能出错(必须通过实例调用方法),效率不如静态(但不会有太大影响)。
如果这些说法太抽象,让我给你一个常见的问题:如果你的框架有一个可以管理所有声音播放的SoundManager,你会如何实现它?
(刚开始接触AudioSource组件的时候,我以为每个声音都会由一个AudioSource来播放。但是后来发现完全没有必要。AudioSource有一个静态的PlayClipAtPoint方法来播放临时的3D音效,还有一个实例方法PlayOneShot来播放临时音效(2D和3D取决于实例的SpatialBlend),如果没有特殊需求,那么一个AudioSource循环播放背景音乐,以上两个方法在游戏中播放特效音频,这对于大多数游戏来说已经足够了。)
那么问题来了:如果你的SoundManager播放声音的方法是静态的,那么代码中必须通过各种方式获取AudioSource组件(创建新组件或者获取特定GameObject下的组件)——因为保存这个组件的变量必须是静态的,所以不能通过Unity的编辑器来赋值。 如果不读代码,用户根本不知道这是一个什么样的组件获取过程。 如果我破坏了这个进程(同名的对象,包括互斥的组件等),那么这个Manager很可能会出现不可预测的异常。
继承 MonoBehaviour 和 RequireComponent(typeof(AudioSource)) 比“为了静态而静态”的代码更加方便和健壮。
其实到这里我们基本可以总结一下什么时候需要使用单例:
1、只要你的类需要将其他组件保存为变量,就必须使用单例;
2、只要需要在Unity编辑器上配置参数,就必须使用单例;
3、只要你的manager需要控制加载顺序,就需要使用单例(比如热更新后加载ResourcesManager);
当然,这些只是“必要”unity中多线程,而不是“必须”。 两者最大的区别在于,一个是书写方便,一个是使用方便。 编写方便的代价是每次调用都添加一个实例,使用方便的代价是放弃面向对象和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、配置文件访问类;
项目中经常需要一些环境相关的配置文件,比如短信通知相关、邮件相关的配置文件。 例如属性文件,这里是读取属性文件配置的示例。 如果使用Spring,可以使用@PropertySource注解来实现。 默认是单例模式。 如果不使用单例,每次都要创建一个新的对象,并且每次都要重新读取配置文件,这极大地影响了性能。 如果使用单例模式,则只需要读取一次即可。 以下是文件访问单例类的简单实现:
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.随着类的加载而执行,只执行一次,并且优先于main函数。 具体来说,静态代码块由类调用。 调用类时,先执行静态代码块,再执行main函数;
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(); } }
他的执行结果如下:
静态块执行...
main函数正在执行
构造代码块执行...
构造函数执行
可以看出他们的执行顺序是:
1.静态代码块
2、主要功能
3. 构造代码块
4.构造函数
利用静态代码块只在类加载时执行,并且只执行一次的特性人物立绘,也可以用来实现单例模式,但它不是延迟加载,也就是说每次类加载时都会主动触发实例化已加载。
以上内容是如何在Unity3D中使用单例模式和静态类。 你学到了知识或技能了吗? 如果您想学习更多技能或者丰富自己的知识库,欢迎关注易速云行业资讯频道。