音乐可视化specinker重构开发日志

 重构前后对比:

音乐可视化specinker重构开发日志 

音乐可视化specinker重构开发日志

由于期末临近,同学哀求老师给时间复习,就推迟到考试结束一星期之后,这也给了充足的时间,对软件进行了一次彻彻底底的重构!!

有多彻底?我重构之前这个项目的代码只界面部分就已经七千多行了,这次重构小到基本控件,大到整个界面,引擎,都重新写了,唯一留下的,还是那个颜色选择控件。

为什么要重构?有三方面原因:

1、性能跟不上,内存占用太多:起初频谱引擎的响应速度为25ms刷新一次,也就是说25ms之内要分析波形,根据波形,绘制轨道,得到轨道后,又要根据属性,绘制图形。而我在代码设计之初并未考虑效率问题,导致软件做好之后频谱有轻微的卡顿,起初我以为是音频数据的问题,因此还特意写了一个缓冲算法,进行平滑,勉强能够用得下去。而内存就更夸张了,每创建一个频谱,内存要多几十MB,而删除之后还不会减少,我明明有把对象析构啊!

2、界面粗糙,操作困难:界面丑不丑大家各有各的看法=.=,操作困难倒是真的,那个时候还没写手势拖动的功能,要移动频谱,只能在那个微调框那里,输入数据或拖动滑动条,手都给弄酸了

3、代码混乱,扩展困难:写代码的时候只管当下想要什么,就写什么,而没有把目标放长远,为以后的扩展给预留位置。

我是如何解决性能和内存问题的:

按照常规的界面,我都是在窗口的绘图事件之中创建画笔,然后进行绘图,这样做常规的界面通过事件绘图,看不出又什么区别,但是我这里25ms刷新一次,每次画笔都要重新初始化,那么就特别耗时,因此,我直接将画笔做为成员,将调整控件的值与画笔的属性进行绑定,那么在绘图的时候就不需要再初始化画笔了,窗口只管绘图就行。而内存方面,是由于Qt的控件删除问题,控件在删除前,必须将其父控件设为null,再调用remove,之后还需调用delete才能删除。

界面问题:

由于这个团队就我一人,且代码工程量不小,既要做UI,也要搞引擎,还得做ppt,初次的设计界面是随手画的,比较普普通通的常规设计软件,一个缩率窗口,然后窗口旁边一些属性,这样做也还行,后来我突然这样想到,既然我做的是桌面频谱,我为什么还要费事费力的搞一个缩率窗口来进行设计,直接在桌面设计,所见即所得不行吗?因为这个东西,我考虑到肯定要花费很多时间,所以歌单管理界面写了一半,干脆直接不要播放器了,直接采集系统音频,况且做播放器真的太没意思了(主要原因还是我找到了采集系统声音的方法)

代码混乱问题:

初次写的项目里就十多个文件的样子,有些cpp中代码量甚至超过了千行,可读性太差,扩展就更不用说了,根本扩展不了,另外就是控件的效率问题,那个时候的控件都是自行绘制的(对,就是通过鼠标键盘事件自己绘制这些控件,这样做的好处就是控件比较个性化,好看!),随之而来的就是效率问题,比如我拖动频谱,那么控件的值也应该进行修改,试想拖动一个控件肯定要触发很多次的控件设值事件,这些事件如果不能快速处理,就会导致我拖动的时候,会有卡顿。因此,对其重构,将控件与频谱进行抽象,以便扩展

重构时使用设计模式解决了以下问题:

问题1:在不同环境下使用这个软件,比如窗口尺寸不同,软件如何自适应?

使用享元模式和单例模式,创建一个Config类,使用饿汉式单例。在类的构建时,动态的去读取这些信息,而软件设计中是通过Config类来进行配置,因此可以使得软件能够适应不同环境。

问题2:如何构建出一套完整的颜色选择控件,使得其中不同的控件可以共同协作调整同一个色值?

使用中介者和观察者模式,构造一个颜色中心(color center),所有的颜色控件都是对它内部的颜色进行修改,当一个颜色控件捕获到一个颜色后,首先修改颜色中心的color值,然后颜色中心发送一个刷新信号,各个控件再刷新其内部的颜色。

问题3:一个被嵌套了很多层的窗口对象,已经“埋”得很深了,这时如果一个外部类使用它,难道一层一层的通过参数传递?

使用单例模式,深刻体会到单例模式的用处,单例模式的巧妙之处不在于“单例”,更为有用的是提供了一个全局的公共访问点,其中很多窗口类都用到了单例,颜色控件中的取色器,颜色板,颜色选择器,显示窗口,以及属性窗口,由于软件中除了主线程之外,还有一个引擎的波形分析线程,并没有涉及到界面操作,因此无需考虑线程安全,又由于所有Qt窗口对象创建之前,必须先构造一个QApplication对象,所以无法使用饿汉单例在类的构建时就创建单例对象。因此,这里用到的所有单例大都是懒汉式。

问题4:很多时候会需要创建“相似”的动画生成器,应该如何处理?

使用原型模式,对动画模式进行深拷贝,动画类存在一个抽象的clone函数,主要是拷贝原型的属性值,以及颜色值。

问题5:每一个微调控件都有各自的值修改信号,如果每一个控件都按各自的信号,那么处理的时候就需要对它们一个一个的进行处理,这无疑是一个非常繁琐的事情,且很容易出错,也不容易排查。

使用了适配器模式,一些控件是自己实现的,一些是控件是使用自带控件。为了让所有的微调控件能够被统一的处理,因此我创建了一个adjuster类,内部提供一个valueChange信号。各个控件只需继承自adjuster,并且连接valueChange信号,即可。

问题6:有时候选择一个动画的属性之后,可能还会需要对这个属性进行细微的设计,比如我选择“点”类型频谱,那么应该可以修改它的“点大小”,如果修改为“线”类型频谱,那么就应该可以修改它的“线宽”。也就是说,可能存在一些动态的属性,那么该怎么解决这个问题?

使用组合模式,使得控件能“包含”控件,当父控件的值变动时,切换相应的子控件。

问题7:只提供一个动画的设计是否使得软件过于“乏力”,能不能再进一步使得整个软件能绘制其他的自定义动画?

的确,单纯的绘制一个类型的频谱动画,最多也就是将这些动画组成不同的形状,用户要不了多久就会失去兴趣。因此我是这样思考动画的扩展:一个动画无非就是连续显示多张图片。所以,要实现一个可定制的动画,需要的就是一个属性调节面板(动画属性+颜色属性)加一个根据这些属性来绘制动画的“图片生成器”。综上,我对项目进行了重构,且将动画向上抽象,构建了一个抽象的动画类。只需提供一个动画的生成函数,以及属性面板,就可扩展额外动画。

重构后的代码框架(头文件)

音乐可视化specinker重构开发日志

频谱动画类UML图(只需按这个抽象类实现动画,可以进行任意扩展)

音乐可视化specinker重构开发日志