
builder模式是一种非常简单且容易理解的设计模式。在我们使用各种三方类库时经常会用到。本篇文章不会具体介绍builder模式。只是随着工作时间增加,感觉到平常我们使用设计模式并不是一蹴而就的,大部分都是随着业务累加或改变,才会依据不同设计模式对代码进行不同程度的调整。最近也是面试了一阵,想着正好可以举一个简单演变的例子,对日常工作中使用的设计模式做一个总结。这次以一个最简单的builder模式为例,看看我之前工作中代码结构是如何演变的。
版权声明:本文为博主原创文章,未经博主允许不得转载。
近期正好面了某知名招聘外企的国内分公司。顺便记录下面试感受。技术一面是远程面试,跟网上说的外企面试差不多,体验非常好,无八股文。有算法但是不难,不会纠结于最优解。交流起来令人非常愉悦。但是如果不出意外的话果然要出意外了。后两轮现场面试体验不是很好。
这篇文章也是由于这次体验不太好的面试,让我觉得做过的东西、学过的技术有时间的时侯最好还是多查阅资料并记录下来。毕竟参加面试的话我们在现场没有资料可以查。如果碰到某些面试官苛刻的提问甚至质疑,以及对旧知识点的考察,只是凭借印象和记忆显然说服力和底气是不太够的(行业大神们除外)。尤其像我这种跳槽后业务和技术点跟之前又不一样的,之前的技术点也并没有时间去更新跟进。
比如这次我介绍完在上家公司使用builder模式解决灵活构造复杂对象和解耦之后(有自己画的简图),面试官连续追问了很多诸如”builder模式怎么能解耦?”、”你这个算是解耦么?”、”你们只是把view分开而已,这怎么叫解耦?”、”你再说说怎么解耦(使用builder模式)”。。。我只能说碰到这种质疑N连的面试机会应该不多,但是万一碰上的话,直接把文章发给面试官让他自己回去研究就好,没必要浪费时间。同时,我也觉得如果作为面试官的话,不要对面试者之前的架构或代码做莫名技术指导。不同公司业务技术栈选择都不尽相同,你觉得不合理在别人公司可能就是比较好的选择。
总结一波,以后自己作为面试官的话也应注意自己的话术,该提示就提示,多聆听,连续同一点的反问会让别人感觉很不好。共勉。
1.业务功能概况
啰嗦了那么多,现在就让我来介绍下此case的业务场景,以及是如何一步步演变成builder模式的。
这是一个广告视频播放的播放器模块的展示层。一般的视频播放器,就像大家平常看剧用的播放器,一般是分为如下几部分:最底层TextureView或SurfaceView,用来播放视频。上面会有各种小的控件,用来实现不同功能。比如声音按钮、播放按钮、loading图标、打底图、封面图、进度条等等,具体根据不同业务需求组件也不同。我之前的广告视频播放器上层控件就是上面那些加上一个广告尾帧页面。广告视频的ViewGroup是需要返回给上层的,上层在需要展示广告的位置addview()。

由于我们商业化广告在自己公司内部APP最开始不是标准SDK(对其他公司是SDK),而是作为一个Module在APP工程里,并且APP广告的接入也是我们商业化团队来做。因此出现了一种特殊情况,就是我们的视频广告在不同广告位展现的UI是有区别的,视频控件在不同位置也有UI等的区别。
这里多插一嘴,如果商业化团队只做SDK开发,那么广告接入应是由APP端(Publisher)自己完成的,SDK只提供统一的功能和样式。比如google的广告SDK–Admob。APP接入ADMOB广告是Publisher(Supply端)研发去做,google只会提供接入文档。与此同时,banner、插屏广告等只会对外提供一种视频广告样式,APP端(Publisher)在哪个页面调用视频样式都是一样的,毕竟google不会对你们APP不同位置的广告做不同UI开发。但是,由于公司特殊性,我们商业化团队承担了广告平台开发和APP接入两个角色,因此会出现不同广告位展示不同视频UI的需求。
2.初始的代码—三个xxxMediaView
我接手视频广告的时侯,之前的需求是只有三个广告位支持视频广告,每个位置UI有些许差异,因此代码内有三个视频广告的ViewGroup,每个ViewGroup之间差异在10%-20%。由于之前业务明确只有三个位置支持视频广告,可能是由于怕几个位置写到一起打架,因此每个位置一个view我认为还算合理。(毕竟不是自己写的也不知道当时情况,暂且这么理解)
3.改进1.0–新的广告位需要支持视频广告–分久必合
随着新的需求接入,有少量之前不支持视频的广告位需要支持。因此每一个位置一个视频MediaView的方案肯定是有问题了。以后还不清楚有多少广告位支持的情况下,view的数量肯定不能是无限的。因此第一个优化改进就是用一个MediaView统一所有视频广告展示。
对于不同位置UI或控件展示不一样的问题,(比如有些位置不显示播放进度,有的位置没有播放按钮-启动页强制自启不暂停播放),在view中通过对不同组件set状态实现。这是最直接且简单的实现方式。比如下面伪代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 public class MediaView extends MediaViewGroup{
private MeidaProgressBar mMediaProgressBar;
private LoadingProgress mLoadingProgress;
private CountDownView mCountDownView;
private ImageView mSoundIcon;
private ImageView mCoverView;
//.....
public void setSoundVisible(boolean visible){
mSoundIcon.setVisible(visible);
}
public void setSoundImage(Drawable drawable){
mSoundIcon.setImageDrawable(drawable);
}
public void setCoverImage(Drawable drawable){
mCoverView.setImageDrawable(drawable);
}
@Override
public void onProgress(int progress){
//示例代码,MediaView为展示层,继承的xxxViewGroup中处理播放器逻辑,这里是播放器进度的回调
//刷新播放进度条
if(mMediaProgressBar != null && mMediaProgressBar.isEnable())
mMediaProgressBar.refresh(progress);
}
......
}
在支持视频广告位数量不太多的情况下,由于展示之间差异不大,所以增加的set方法还不是很多,因此对于紧急业务来说可以接受。
4.改进2.0–大量广告位需要支持视频广告–合久必分
业务发展一段时间后,尤其是前几个视频广告数据跑出效率之后,大部分都广告位要支持视频。而随着视频尾帧需求的加入,这时候这一个MediaView里有超过12个控件,初始化+各种visible和背景、颜色的set方法都堆在这一个类中,再加上视频回调接口也在此类中实现,比如刷新进度条等,会使这一个类过于冗长,可读性变差。而且每次创建MediaView对象时,new之后会调用一长串的set以满足不同位置不同的样式,实在是麻烦。
这里解释下视频尾帧。此需求是在视频播放结束后展示广告主的一些内容,如:icon,title、message、button、重播按钮等。用于引导用户二次点击广告。也是因为尾帧的加入使得整个ViewGroup中的组件过于多了。
由于每次new一个MediaView对象之后会调用一系列的set,顺其自然地就能想到一点:能否在创建对象的时侯将各个组件根据需要加入此类,每个或者几个组件放在一个独立类中,这样此MediaView中就不会有各组件及业务逻辑,这个MediaView类的代码就会大大减少。且每个组件在自己的类中处理各自展示和回调(通过接口返回)。构造每个对象时也能根据需求加入需要的组件,使得构建对象更加的灵活。虽然类会变多,但是可读性和扩展性会大大增强。且增加的类比需要展示视频的广告位置还是要少的多。
根据此思路,很容易想到一个方案:对MediaView增加构造器,通过构造器参数传入需要的组件。但是如果按照此思路的话,构造器参数至少要12个, 且还需要多个构造函数。一般构造器参数4~5个就已经算多的了,这种思路肯定无法实施。于是第二个思路就很顺其自然了:使用builder模式set个组件后build出对象,既解决构造器参数过多问题也能将组件从ViewGroup中拆到各自组件类中。
到这里builder模式的大概思路就出来了。但是由于继承了ViewGroup,并不是简单对变量赋值,这里还是做了一些改变的。在set时执行的是addView操作,并且会把接口传到mediaView构造器,以便通过接口处理视频回调。看起来有些复杂,实际很简单,后面有示例代码。
再说一些我个人的一些使用设计模式的经验吧。我在决定用任何设计模式或者架构的时侯是拒绝照本宣科的。不是觉得哪个流行、哪个高级或者大公司都在用就用哪个。对于设计模式的实现在不同业务中会做一些自己的变化。当然这些变化有的也不见得很好。主要我觉得设计模式和架构都是根据当前及可预见未来内的业务需求进行使用的。这是一个一步步演变一步步优化的过程。在业务变化过程中,设计模式和架构也是不断适应和变化的。没有最好的架构,只有适应当前业务规模和逻辑的架构。
这次的builder模式也是业务增加带来的代码结构变化。如果只有三个广告位,我认为只用三个ViewGroup也是很OK的。
对于解耦这件事,我认为解耦不仅仅是说view和module层分开才叫解耦合。我认为解耦合其实有很广泛的意思,两个业务逻辑分开两个类也算是解耦,将一个view的内部组件拆分到不同类,每个类只操作和回调一个或几个组件,相互之间不受影响也算是一种解耦合。因此,我认为这个case里的builder模式实现了MediaView各组件间的解耦一点问题都没有。
这个问题也是某位面试官一直质疑我的一点,在这里记录下。虽然我也不知道这种简单问题有啥好质疑的。下面画了张图,大体总结下我们这个ViewGroup的类的拆分。
这里将我们所有组件根据业务功能划分成了6个大组件。在new每个MediaView的时候通过set不同组件实现不同功能需求的MediaView。也很好的确保了每个MediaView只包含相应功能的组件,减少冗余。下面是代码:
1 | public class MediaView extends MediaViewGroup{ |
下面是MiddleFrame事例代码,从MediaView中分离出自己类的的业务职责。
1 | public class MiddleFrame implements MiddleFrameInterface{ |
以上就是我使用builder模式的一些总结。由于离职有一段时间了,没法写出完整的代码。大家意会即可。主要是分享下自己使用设计模式的演变过程。毕竟每个人遇到的业务逻辑都会有所区别。大家应该按照自己业务所需作出自己的改变。