架构设计深度解析:策略模式 + 抽象工厂在UI适配中的高级应用
架构设计深度解析:策略模式 + 抽象工厂在UI适配中的高级应用
代码:
// 在 BasePageConfig中 定义了很多抽象方法abstractclassBasePageConfig{companionobject{constvalCOLUM_COUNT_ONE=1constvalCOLUM_COUNT_TWO=2constvalCOLUM_COUNT_THREE=3constvalCOLUM_COUNT_FOUR=4constvalCOLUM_COUNT_FIVE=5constvalCOLUM_COUNT_SIX=6constvalCOLUM_COUNT_EIGHT=8constvalCOLUM_COUNT_TEN=10constvalCOLUM_COUNT_TWENTY=20funget()=if(ViewDelegate.isLandW2560()){LandPageConfig}else{PortPageConfig}}abstractfunrecommendGetGridLayoutManager(context:Context,recyclerView:RecyclerView,bannerWidgetStyle:Int?):GridLayoutManagerabstractfunrecommendSongFolderListMax():IntabstractfunrecommendSongInfoListMax():IntabstractfunmusicHouseGetGridLayoutManager(context:Context,recyclerView:RecyclerView):GridLayoutManager}// 在具体的实现类 LandPageConfig,PortPageConfig中,来实现,会对每个页面,设置具体是多少span/列,写的完整,严谨,很美妙LandPageConfig.kt:objectLandPageConfig:BasePageConfig(){overridefuncreateDecorationConfig():IDecorationConfig{returnLandDecorationConfig}overridefunrecommendGetGridLayoutManager(context:Context,recyclerView:RecyclerView,bannerWidgetStyle:Int?):GridLayoutManager{returnGridLayoutManager(context,COLUM_COUNT_TEN).apply{spanSizeLookup=object:GridLayoutManager.SpanSizeLookup(){overridefungetSpanSize(position:Int):Int{valadapter=recyclerView.adapteras?RecommendAdapterreturnwhen(adapter?.getItemViewType(position)){RecommendData.TYPE_SONG_FOLDER_SUPPLIER_STATION->COLUM_COUNT_TWO RecommendData.TYPE_SONG_ITEM->COLUM_COUNT_FIVE RecommendData.TYPE_BANNER->{if(bannerWidgetStyle==1){COLUM_COUNT_TEN}else{COLUM_COUNT_FIVE}}else->{COLUM_COUNT_TEN}}}}}}overridefunrecommendSongFolderListMax()=COLUM_COUNT_TENoverridefunrecommendSongInfoListMax()=COLUM_COUNT_EIGHToverridefunmusicHouseGetGridLayoutManager(context:Context,recyclerView:RecyclerView):GridLayoutManager{returnGridLayoutManager(context,COLUM_COUNT_TEN).apply{spanSizeLookup=object:GridLayoutManager.SpanSizeLookup(){overridefungetSpanSize(position:Int):Int{valadapter=recyclerView.adapteras?MusicHouseAdapterreturnwhen(adapter?.data?.get(position)?.itemType){MusicHouseItem.TYPE_TITLE_MORE->COLUM_COUNT_TEN MusicHouseItem.TYPE_NEWEST_SONG_ITEM->COLUM_COUNT_FIVEelse->COLUM_COUNT_TWO}}}}}总结: base中把每个页面,对应一个函数名称,在实现类 LandPageConfig,PortPageConfig中,写需要多少span,也就是占多少列它属于典型的**“策略模式 + 抽象工厂”**在 UI 适配中的高级应用。
我也觉得这段代码写得很好,好在以下三点:
1. 将“业务逻辑”与“布局参数”彻底解耦
在一般的代码里,我们经常会在 Fragment 里面写大量的if (isLandscape) 5 else 2。
而这段代码把**“一共有几列”、“某个 Item 占几格”**这些琐碎的数值,全部抽离到了LandPageConfig和PortPageConfig中。
- 好处: 你的 Activity 或 Fragment 变得极其干净,它只需要调用
Config.get().recommendGetGridLayoutManager(),完全不需要关心当前是横屏还是竖屏。
2. 利用 SpanSizeLookup 实现极高的灵活性
看LandPageConfig里的这段逻辑:
overridefungetSpanSize(position:Int):Int{returnwhen(adapter?.getItemViewType(position)){TYPE_SONG_ITEM->COLUM_COUNT_FIVE// 歌曲占 5 格(一行俩)else->COLUM_COUNT_TEN// 其他占满 10 格(一行一个)}}这里的“美感”在于:它把 RecyclerView 的复杂布局转换成了简单的数学题。
通过COLUM_COUNT_TEN(总共10列)作为分母,不同的 Item 可以自由分配:
- 想一行显示 2 个?那就占 5 格。
- 想一行显示 5 个?那就占 2 格。
- 想占满全屏?那就占 10 格。
这种以最小公倍数作为总列数的思想,让 UI 调整变得像拼图一样简单。
3. 强迫症级别的“严谨性”
- 单例化(Object):
LandPageConfig用了object关键字,这意味着这些配置在内存中只有一份,不会重复创建对象,非常省内存。 - 类型安全:在 Base 类里定义
abstract fun,强制子类必须实现。这样如果你以后要加一个“折叠屏配置”,编译器会立刻提醒你:“嘿,你还没给折叠屏设置歌曲列表的最大值呢!”——这避免了低级错误。
核心感悟:配置化思想
你发现的这种“美”,其实是**“配置化”**的思想。
- Base 层:定义了规则(有哪些页面需要配置?)。
- 实现层:填入数值(具体这个环境下的列数是多少?)。
- 调用层:只负责执行。
这种结构不仅写起来爽,后期维护更爽:如果老板说“横屏下推荐位想从 2 列改成 3 列”,只需要去LandPageConfig改一个数字,其他几千行代码动都不用动。
