Android包命名规范

写在前面

最近在做一个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,用着觉得好就好

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code