迷思——哪有什么岁月静好,都是有人替你负重前行
起因是, 笔者想要在Unity中实现一些基于UGUI的拖拽效果, 于是开始去思考UGUI是如何实现响应输入的, 接着了解到Unity的EventSystem和一系列EventHandler接口(IPointerClickHandler、 IDragHandler等)来驱动整个System的运转. 笔者在这篇博客中并非是要讨论EventSystem的具体实现. 而是想陈述自己对EventHandler这些接口在UGUI体系中扮演角色的一些迷思. 作为UGUI的使用者, 我只关心如何实现自己想要的功能. 如果我想让一个UI元素可以响应点击, 那我就要创建一个继承自MonoBehaviour的脚本, 挂载到这个UI元素上, 并且还要实现IPointerClickHandler接口, 这样就结束了, 至于怎么样在运行时获取到这个UI元素上的脚本实例, 以及如何触发接口中的方法, 我不关心, Unity会帮我处理. 这就是我对接口最初的理解, 我只负责接口行为的定义, 我不负责接口行为的调用. 于是有一段时间我经常会遇到这种处境: 在业务的开发中发现一些类型中能抽象出一部分行为封装成接口, 但有可能这些类根本就不是同一个概念下的东西, 或者, 正是由于这种 只考考虑抽离行为 而不考虑接口调用时机和接口持有者的数据组织格式, 导致绝大部分抽离行为封装接口的操作都十分多余.
接口的行为不止于此 我记得在刚入行的时候, 一个程序员前辈跟我说, 写代码是一个十分繁琐的过程, 但如果你觉得某一部分代码写起来好像不用那么繁琐就能实现你的功能, 那有可能是有人在你不知道的地方帮你做了很多事情. 我上面举的EventSystem就是这样一个例子, UGUI系统通过对外暴露EventHandler接口的方式, 在内部有能够持有接口实例的方法, 你不需要去关心UGUI系统内部是如何持有接口示例的. YooAsset自定义打包Step也是类似的思想, 二者都是通过某种方式持有了外部的对象实例, Unity是通过运行时持有并遍历所有的GameObject, 找到上面的EventHandler来持有它们, 而YooAsset则是在Init时将需要的buildstep实例注册到YooAsset内部去. 两者的思想或许可以表述如下: 外部定义行为, 内部控制流程.
最近笔者想要给项目的换皮活动也做一个类似的, 一个活动换皮要有下面几个固定的步骤 导入资源、更新配置表, 定义一个IStep接口, 接口内有一个Action行为, 调用方可以实现多个继承IStep接口的类, 比如导入资源类、更新配置表类, 然后在Action行为中就可以实现是如何导入资源的、如何更新配置表的. 然后将导入资源类和更新配置表类再注册到一个序列化的数据结构或者硬编码的脚本中, 在执行换皮的时候就是从序列化数据或者硬编码脚本中拿到这一套流程信息 传入到 我这套换皮框架中, 外部不用管我内部是如何驱动这套流程信息运转的, 这样的模式和上面举的UGUI、YooAsset的例子大同小异.
Unity协程、C#的Task也是如此, 只因我被“保护”得太好了, Unity的协程和Task都很好用, 丢进去一个异步的任务, 让他自己在那里执行就好了, 可是异步任务本质上是怎么执行的呢? Unity协程依赖于MonoBehaviour的Update机制, Task依赖于C#内部的线程池. 背后都会有一个间隔一定时间轮询的逻辑, 只是我不需要关心这段逻辑罢了
我有段时间看了大量讲述C#Task的文章, 现在再去回想其实还是不甚理解, 多去想想
定义接口的目的, 让一些类型具有某些行为, 并且可以通过声明接口类型, 来持有接口的实例, 一个实例的对象既可以是该实例的class类型, 也可以是其实现的任意一个接口的类型.
下面列举了笔者在开发过程中遇到的
- C#中的非托管资源在使用的时候可以实现IDisposable接口, 在Dispose接口中, 给了开发者比较统一便捷的方式对不同的非托管资源进行释放的一个点, 另外其中
- 语法糖foreach, 有一个IEnumerable和IEnumerator, 使用IEnumerable标识一个类型的身份,
因此 接口在设计隔离性、统一操作、标识统一身份等场景下能发挥作用.