简单动画系统随笔

最近重构动画系统,虽然只是一个小系统,不过在设计的过程中发现自己在宏观层面的架构搞惯了,这种体量小却结构精巧(和宏观层比就像从模块间到模块内)的也有一定考验性。架构本身不分大小,在一些基本原理上是一致的。随笔记录自己的一些思路和想法:

  1. 一个抽象动画过程Animation应该有这些通用的属性/接口:
    1. 动画状态 (初始/运行/暂停/停止/结束),结束和停止在结果表现上看似乎比较相近,但是因为其对应的现实情况是不一样的,因此有必要进行区分,外界可能需要知道一个动画究竟是被中途停止的还是完整结束的。
    2. 动画状态的查询功能。
    3. 对外的触发驱动接口,动画过程是被动式驱动,需要外界每次主动驱动它更新其内部的状态和信息。
    4. 动画运行的累计时间。
    5. 动画运行的累计驱动计数。这个属性其实相对特殊,不过考虑到一些动画本身不关心时间流逝,只关心被驱动几次,这个参数也是有意义的。
    6. 控制接口(start/pause/resume/stop)。
  2. 常规思考中,觉得动画进度/动画持续时间应该作为动画过程的属性, 但这些反而不是一个抽象动画过程应该包含的,因为抽象动画过程是没有持续时间这个概念的,一个动画过程的持续时间是不可预估的,这些属性应该在下面的某个具体子类中被包含,比如可以定义一个FixDurationAnimation来表示一个持续时间可期的动画过程。
  3. 再进一步,动画过程在使用中还会有这些诉求:
    1. 播放几次: 这其中还包括了循环播放(这也反应了2所提出的动画过程本身的持续时间不可预估)
    2. 播放延迟
    3. 并行播放
    4. 串行播放
    5. 插值器播放
  4. 新功能的实现,从设计上讲无外乎组合和继承两种实现方式,因为有抽象基类Animation的存在,继承是不可避免的,但是,类似于并行/串行播放这种需要对动画进行组合(更进一步,嵌套组合)的需求,单单继承不能满足,Action之间的组合也是需要的。那么综合来看,需要两种模式: 装饰模式和组合模式。 装饰模式可以实现对单个Animation的扩展,而组合模式则是为了实现Animation的组合需求。
  5. 装饰模式是组合和继承的混血儿,装饰模式相比纯组合的优势在于其装饰了新功能的类和被装饰的类仍属于一个类系,这样对外屏蔽了纯组合导致的类差异和细节。相比纯继承的优势则在于其又有一定的组合性,新功能之间的叠加组合可以通过装饰叠加来实现,而纯继承受限于继承树本身的结构限制,做不到对不同扩展功能的叠加组合。
  6. 组合模式在对外表现上和装饰模式类似,都是在提供新功能的前提下,保留了同一类系这一特性,使得外界可以无缝的使用。和装饰模式不同的地方在于组合模式通过被组合对象之间的联动或者组织来实现新功能,而装饰模式则是通过对被装饰者方法的拦截来实现新功能。当然了,如果使用组合模式时只最多每个组合中只包含一个对象,那么这个时候组合模式就基本和装饰模式相近,只不过组合基本被限定为只有一层装饰,但是也基本失去了使用组合模式的意义。从这个角度来看,组合模式和装饰模式在血缘上是很接近的。
  7. 装饰模式一个最明显的副作用是装饰类的方法实现基本都要将调用转发给被装饰的对象,如果类的接口很多,写起来很蛋疼。这一点基本上不可避免,只能通过再引入将扩展功能作为组合的方式引入得到缓解,但是这样,装饰模式基本就退化为一个warpper,因为扩展功能本身的真正实现已经不在其内部了。
  8. 回到Animation的功能扩展,重复播放/延迟播放的作用粒度显然只是单个Animation,那么就考虑使用装饰模式来实现这些功能的扩展。不过这里有一点细节需要商榷的:上面说了,Animation内部维护了状态以及状态查询,而装饰类则也会作为一个Animation存在,按照常规的装饰模式实现,装饰类本身基本不需要维护自己的状态,而是完全用被装饰的对象状态作为自己的状态,以此实现透明调用。不过涉及到具体case,比如延迟播放,在延迟播放这段时间内,从延迟播放动画来看,是可以认为其是RUNNING状态,但是从被装饰的对象来看,其还没有开始运行,只是INIT状态,装饰类仍然需要维护自己的状态,根本原因是,装饰带来的新功能中包含了对状态逻辑的改写。相应的重复播放/插值器播放因为装饰功能被没有改写状态逻辑,因此这些装饰类倒是不必维护自己的状态。但是,从逻辑实现的统一性上考虑,所有的装饰类均单独维护自己的状态,外部的状态查询不再转发给被装饰对象。当然了,这样带来的一个成本就是装饰者和被装饰者状态之间的同步(其实有些case下本身是不同步的)。
  9. 通过装饰模式扩展了一部分功能后,还有一部分需要组合的功能,就需要借助于组合模式,组合模式本身实现简单,并且如同之前分析的组合模式的同源性,组合模式扩展类可以很好的和装饰类一起协同工作实现横向和纵向扩展,其中,组合是横向扩展,装饰是纵向扩展。组合模式在Animation中的实现也需要考虑状态的问题,其在这一点上要比装饰模式扩展Animation复杂,因为其内部会有多个Animation,因此对于组合模式Animation来说,其内部最好维护自有状态。
  10. 在决定了状态是否扩展类自己维护以后,就考虑控制接口的具体实现了。而上面我们已经统一了口径,扩展Animation类均维护自己的妆台,因此,所有改变状态的控制接口,对于每个扩展Animation类来说,都需要确实可以修改扩展Animation类的状态,而不是直接将这个状态修改请求转发给内部包含的Animation。这样,控制接口改变状态本身应该作为基类的非虚函数存在,因为在我们的前提下,所有Animation均需要这个功能,至于进一步的转发到内部Animation这个扩展,则通过基类的非虚函数中调用虚函数来实现,比如pause()本身是一个非虚函数,其操作除了将状态设计为PAUSED外,还会调用一个onPause()虚函数。不这样实现的话,如果直接把pause()作为虚函数,考虑到扩展Animation需要维护自己的状态,每个子类的pause()实现都需要调用基类的pasue(), 比较臃肿。

你可能感兴趣的:(Desigin,&,Struct,项目经历)