跳到主要內容
黯羽輕揚每天積累一點點

Android 包命名規範

免費2016-02-28#Android#package by layer#package by feature#安卓项目分层#android package naming convention

準確地說是 Android 類別組織規範,不是指類似於 com.example.app 的 package name,而是 util、dao、service 之類的 package 定義(用 package 來組織 class,說白了就是怎麼分層)

寫在前面

最近在做一個 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 自然放在 featureA 下,小李的 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 and widget,相关的Adapter也放这里
            │
            ├─util
            │      实现工具类,提供静态方法
            │
            ├─feature1
            │      Item.java                定义model
            │      ItemHelper.java          实现modelHelper
            │      feature1Activity.java    定义UI
            │      feature1DAO.java         私有db操作
            │      feature1Utils.java       私有工具函数
            │      ...其它私有class
            │
            ├─...其它feature

當然,這樣的程式碼組織方案不僅僅適用於 Android,用著覺得好就好

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論