写在前面
最近在做一个Android小游戏,Activity略多,按view, com, core, dao, service, util等等来分层总感觉不舒服,出去翻了一会儿才发现这样划分包名已经不是主流做法了
一.PBL(Package By Layer)
按层分包组织class,也就是传统做法,分为view, com, core, dao等等包,把职能相同的class放在一起
在default package下写完整个项目的所有class也不是不可以,但太多不相关的class挤在一起,且不说可维护性什么的,单是自己看着也难受
src
└─net
└─ayqy
└─app_gowithme
SearchPlan.java
ShowDetail.java
ShowMap.java
ShowRoute.java
// 或者稍微好一点点的
src
└─net
└─ayqy
└─app_notepad
DAO.java
Dialog.java
MainActivity.java
myEditText.java
ShowList.java
P.S.windows下,tree /f
可以生成上面的东西
笔者习惯的包命名方法(或者说是类组织方法)是按照class职能(所谓的PBL)来分,比如:
src
│ summary.txt
│
└─net
└─ayqy
└─app_rsshelper
│ MainActivity.java
│ SettingActivity.java
│
├─com
├─core
│ Consts.java
│ Urls.java
│
├─dao
├─service
│ HtmlFetcher.java
│ JsInvoker.java
│ MainServ.java
│ RssFetcher.java
│
├─util
│ HtmlHelper.java
│ InputStream2String.java
│ RssHelper.java
│
└─vo
RssItem.java
各层职能如下:
default package
View层,定义Activity只实现UI展示,以及跳转
com(component)
自定义组件,包括自定义View和View组合
core
核心类,定义配置数据以及常量
dao(data access object)
db操作类,包括dbHelper
service
每个Activity对应一个Service,负责实现该Activity的业务逻辑,此外Service依赖的其它与Activity密切相关的类(需要Activity的context,比如JsInvoker)也在这里定义
util
工具类,包括modelHelper(比如RssHelper)
vo(value object)
model,或者说是bean,只包含数据结构定义以及getter/setter(需要的话还可以有equals、compareTo等等,反正尽量保持model纯粹)
笔者先接触了JSP,所以一直认为这套分层方法挺好,在Android里就照搬过来了(甚至搬到了WinForm和WPF),所幸项目规模都很小,没觉得有什么不妥,因为没有第二个方案可选
PBL的缺点如下:
package内低内聚低模块化
通常同一个包下的各个class彼此不相关,也可能永远不会相关
package之间高耦合
通常在一个class里需要import来自不同package的一堆东西
码起来麻烦
实现一块功能需要编辑不同目录下的多个文件,经常用IDE开一堆标签页还都舍不得关闭,因为关了不好找
同样,删起来也麻烦,在几个包下找来找去,一不小心就出错
二.PBF(Package By Feature)
按功能(模块)分包组织class,按app功能分为login、feedback、settings、orders、shipping等等,例如:
// 医药app
src
└─com
└─domain
└─app
├─doctor
│ DoctorAction.java - an action or controller object
│ Doctor.java - a Model Object
│ DoctorDAO.java - Data Access Object
│ database items (SQL statements)
│ user interface items (perhaps a JSP, in the case of a web app)
│ ...不只能放java代码,只要是该功能相关的都应该放在这里
│
├─drug
├─patient
├─report
├─security
├─webmaster
└─util
简单地说,每块功能对应一个package,包名就是功能名,除了通用的类定义在util之类的package下之外,实现该功能的所有类都在该package下
好处很明显,如下:
package内高内聚,package间低耦合
哪块要添新功能,只改某一个package下的东西
按class职能分层(PBL)降低了代码耦合,但带来了package耦合,要添新功能,需要改model、dbHelper、view、service等等,需要改动好几个package下的代码,改动的地方越多,越容易产生新问题,不是吗?
按功能分包(PBF),featureA相关的所有东西都在featureA包,feature内高内聚高度模块化,不同feature之间低耦合,相关的东西都放在一起,还好找
package有私有作用域(package-private scope)
你负责开发这块功能,这个目录下所有东西都是你的
PBL的方式是把所有工具方法都放在util包下,小张开发新功能时候发现需要一个xxUtil,但它又不是通用的,那应该放在哪里?没办法,按照分层原则,我们还得放在util包下,好像不太合适,但放在其它包更不合适,功能越来越多,util类也越定义越多。后来小李负责开发一块功能时发现需要一个xxUtil,同样不通用,去util包一看,怎么已经有了,而且还没法复用,只好放弃xx这个名字,改为xxxUtil……因为PBL的package没有私有作用域,每一个包都是public(跨包方法调用是很平常的事情,每一个包对其它包来说都是可访问的)
如果是PBF,小张的xxUtil自然放在feautreA下,小李的xxUtil在featureB下,如果觉得util好像是通用的,就去util包看看要不要把工具方法添进xxUtil,class命名冲突没有了
PBF的package有私有作用域,featureA不应该访问featureB下的任何东西(如果非访问不可,那就说明接口定义有问题)
很容易删除功能
统计发现新功能没人用,这个版本那块功能得去掉
如果是PBL,得从功能入口到整个业务流程把受到牵连的所有能删的代码和class都揪出来删掉,一不小心就完蛋
如果是PBF,好说,先删掉对应包,再删掉功能入口(删掉包后入口肯定报错了),完事
高度抽象
解决问题的一般方法是从抽象到具体,PBF包名是对功能模块的抽象,包内的class是实现细节,符合从抽象到具体,而PBL弄反了
PBF从确定AppName开始,根据功能模块划分package,再考虑每块的具体实现细节,而PBL从一开始就要考虑要不要dao层,要不要com层等等
只通过class来分离逻辑代码
PBL既分离class又分离package,而PBF只通过class来分离逻辑代码
没有必要通过package分离,因为PBL中也可能出现尴尬的情况:
├─service │ MainServ.java
按照PBL,service包下的所有东西都是Controller,应该不需要Serv后缀,但实际上通常为了码起来方便,直接import service包,Serv后缀是为了避免引入的class和当前包下的class命名冲突,当然,不用后缀也可以,得写清楚包路径,比如
new net.ayqy.service.Main()
,麻烦而PBF就很方便,无需import,直接
new MainServ()
即可package的大小有意义了
PBL中包的大小无限增长是合理的,因为功能越添越多
而PBF中包太大(包里class太多)表示这块需要重构(划分子包)
三.PBF具体实践(Google I/O 2015)
最有代表性的是Google I/O 2015,结构如下:
java
└─com
└─google
└─samples
└─apps
└─iosched
│ AppApplication.java 定义Application类
│ Config.java 定义配置数据(常量)
│
├─about
│ AboutActivity.java
│
├─appwidget
│ ScheduleWidgetProvider.java
│ ScheduleWidgetRemoteViewsService.java
│
├─debug
│ │ DebugAction.java
│ │ DebugActivity.java
│ │ DebugFragment.java
│ │
│ └─actions
│ DisplayUserDataDebugAction.java
│ ForceAppDataSyncNowAction.java
│ ForceSyncNowAction.java
│ ...
│
├─explore
│ │ ExploreIOActivity.java
│ │ ExploreIOFragment.java
│ │ ExploreModel.java
│ │ ...
│ │
│ └─data
│ ItemGroup.java
│ LiveStreamData.java
│ MessageData.java
│ ...
│
├─feedback
│ FeedbackApiHelper.java
│ FeedbackConstants.java
│ FeedbackHelper.java
│ ...
│
├─framework
│ FragmentListener.java
│ LoaderIdlingResource.java
│ Model.java
│ ...定义interface并实现
│
├─gcm
│ │ GCMCommand.java
│ │ GCMIntentService.java
│ │ GCMRedirectedBroadcastReceiver.java
│ │ ...
│ │
│ └─command
│ AnnouncementCommand.java
│ NotificationCommand.java
│ SyncCommand.java
│ ...
│
├─io
│ │ BlocksHandler.java
│ │ HandlerException.java
│ │ HashtagsHandler.java
│ │ ...处理model
│ │
│ ├─map
│ │ └─model
│ │ MapData.java
│ │ Marker.java
│ │ Tile.java
│ │
│ └─model
│ Block.java
│ DataManifest.java
│ Hashtag.java
│ ...
│
├─map
│ │ InlineInfoFragment.java
│ │ MapActivity.java
│ │ MapFragment.java
│ │ ...
│ │
│ └─util
│ CachedTileProvider.java
│ MarkerLoadingTask.java
│ MarkerModel.java
│ ...
│
├─model
│ ScheduleHelper.java
│ ScheduleItem.java
│ ScheduleItemHelper.java
│ ...定义model以及实现model相关操作
│
├─myschedule
│ MyScheduleActivity.java
│ MyScheduleAdapter.java
│ MyScheduleFragment.java
│ ...
│
├─provider
│ ScheduleContract.java
│ ScheduleContractHelper.java
│ ScheduleDatabase.java
│ ...实现ContentProvider
│ (也在此处定义provider依赖的其它类,比如db操作)
│
├─receiver
│ SessionAlarmReceiver.java
│
├─service
│ DataBootstrapService.java
│ SessionAlarmService.java
│ SessionCalendarService.java
│
├─session
│ SessionDetailActivity.java
│ SessionDetailConstants.java
│ SessionDetailFragment.java
│ ...
│
├─settings
│ ConfMessageCardUtils.java
│ SettingsActivity.java
│ SettingsUtils.java
│
├─social
│ SocialActivity.java
│ SocialFragment.java
│ SocialModel.java
│
├─sync
│ │ ConferenceDataHandler.java
│ │ RemoteConferenceDataFetcher.java
│ │ SyncAdapter.java
│ │ ...
│ │
│ └─userdata
│ │ AbstractUserDataSyncHelper.java
│ │ OnSuccessListener.java
│ │ UserAction.java
│ │ ...
│ │
│ ├─gms
│ │ DriveHelper.java
│ │ GMSUserDataSyncHelper.java
│ │
│ └─util
│ UserActionHelper.java
│ UserDataHelper.java
│
├─ui
│ │ BaseActivity.java
│ │ CheckableLinearLayout.java
│ │ SearchActivity.java
│ │ ...BaseActivity以及自定义UI组件
│ │
│ └─widget
│ AspectRatioView.java
│ BakedBezierInterpolator.java
│ BezelImageView.java
│ ...自定义小UI控件
│
├─util
│ AboutUtils.java
│ AccountUtils.java
│ AnalyticsHelper.java
│ ...工具类,提供静态方法
│
├─videolibrary
│ VideoLibraryActivity.java
│ VideoLibraryFilteredActivity.java
│ VideoLibraryFilteredFragment.java
│ ...
│
└─welcome
AccountFragment.java
AttendingFragment.java
ConductFragment.java
...
上面除了按feature命名的package外,也定义了类似于layer的package,如下:
framework
io
model
provider
receiver
service
ui
util
对比一下PBL的一般规范,如下:
PBL一般规范 | Google I/O 2015 |
---|---|
activities(页面用到的Activity类) | feature/ |
base(页面中每个Activity类共享的可以写成一个BaseActivity类) | ui |
adapter(页面用到的Adapter类) | 通用的在ui里,不通用的在feature/ |
tools(公共工具方法类) | util |
bean/unity(元素类) | model |
db(数据库操作类) | 数据库操作在provider里,数据处理(比如json解析)在io里 |
view/ui(自定义的View类) | ui |
service(Service服务) | service |
broadcast(Broadcast服务) | feature/ |
三.总结
参考Google I/O 2015的代码结构,PBF具体可以这样做:
src
└─com
└─domain
└─app
│ Config.java 配置数据、常量
│
├─framework
│ 定义interface以及相关基类
│
├─io
│ 数据定义(model)、数据操作(比如json解析,但不包括db操作)
│
├─model
│ 定义model(数据结构以及getter/setter、compareTo、equals等等,不含复杂操作)
│ 以及modelHelper(提供便于操作model的api)
│
├─provider
│ 实现ContentProvider,及其依赖的db操作
│
├─receiver
│ 实现Receiver
│
├─service
│ 实现Service(比如IntentService),用于在独立线程中异步do stuff
│
├─ui
│ 实现BaseActivity,以及自定义view和widget,相关的Adapter也放这里
│
├─util
│ 实现工具类,提供静态方法
│
├─feature1
│ Item.java 定义model
│ ItemHelper.java 实现modelHelper
│ feature1Activity.java 定义UI
│ feature1DAO.java 私有db操作
│ feature1Utils.java 私有工具函数
│ ...其它私有class
│
├─...其它feature
当然,这样的代码组织方案不仅仅适用于Android,用着觉得好就好