본문으로 건너뛰기

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 의단점은 다음과 같습니다:

  • 패키지 내 응집도가 낮고 모듈화가 낮음

일반적으로 같은 패키지 내의 각 class 는 서로 관련이 없거나, 영원히 관련이 없을 수도 있음

  • 패키지 간 결합도가 높음

일반적으로 한 class 내에서 서로 다른 패키지에서 많은 것을 import 해야 함

  • 코딩하기 번거로움

하나의 기능을 구현하려면 서로 다른 디렉토리의 여러 파일을 편집해야 하며, IDE 에서 많은 탭을 열어도 닫기 아까운 경우가 많습니다. 닫으면 찾기 어렵기 때문입니다

마찬가지로 삭제하기도 번거로우며, 몇 개의 패키지를 찾아다녀야 하고, 조금만 부주의하면 실수가 발생합니다

이.PBF(Package By Feature)

기능 (모듈) 에 따른 패키징으로 class 를 구성하며, app 의 기능에 따라 login, feedback, settings, orders, shipping 등으로 나눕니다. 예를 들어:

// 의료 앱
src
└─com
    └─domain
        └─app
            ├─doctor
            │      DoctorAction.java - 액션 또는 컨트롤러 객체
            │      Doctor.java - 모델 객체
            │      DoctorDAO.java - 데이터 액세스 객체
            │      데이터베이스 항목 (SQL 문)
            │      사용자 인터페이스 항목 (웹 앱의 경우 아마도 JSP)
            │      ...Java 코드뿐만 아니라 이 기능과 관련된 모든 것을 여기에 배치
            │
            ├─drug
            ├─patient
            ├─report
            ├─security
            ├─webmaster
            └─util

간단히 말해, 각 기능에 해당하는 하나의 패키지가 있으며, 패키지 이름은 기능 이름이며, 공통 클래스는 util 등의 패키지에서 정의되고, 해당 기능을 구현하는 모든 클래스는 해당 패키지 내에 있습니다

장점은 매우 명확하며, 다음과 같습니다:

  • 패키지 내 응집도가 높고 패키지 간 결합도가 낮음

어떤 부분에 새 기능을 추가하든 특정 패키지 내의 것만 수정하면 됨

class 기능에 따른 계층화 (PBL) 는 코드 결합도를 낮추지만 패키지 결합도를 가져옵니다. 새 기능을 추가하려면 model, dbHelper, view, service 등을 수정해야 하며, 여러 패키지의 코드를 수정해야 합니다. 수정할 곳이 많을수록 새로운 문제가 발생하기 쉽습니다不是吗?

기능에 따른 패키징 (PBF) 의 경우, featureA 와 관련된 모든 것이 featureA 패키지에 있으며, feature 내에서는 높은 응집도와 높은 모듈화를 가지고, 다른 feature 간에는 낮은 결합도를 가지며, 관련된 것을 모두 함께 배치하여 찾기 쉽습니다

  • 패키지에는 프라이빗 스코프 (package-private scope) 가 있음

당신이 이 기능의 개발을 담당한다면, 이 디렉토리 내의 모든 것이 당신의 것입니다

PBL 방식은 모든 유틸리티 메서드를 util 패키지下に 배치합니다.小张이 새 기능을 개발할 때 xxUtil 이 필요하지만 공통적이지 않다면 어디에 배치해�� 할까요? 어쩔 수 없이 계층화 원칙에 따라 util 패키지下に 배치해야 하지만, 적절하지 않아 보이지만 다른 패키지에 배치하는 것은 더욱 적절하지 않습니다. 기능이 점점 많아지고 util 클래스도 점점 더 많아집니다. 이후 小李가 기능을 개발할 때 xxUtil 이 필요하지만 공통적이지 않아 util 패키지를 확인해보니 이미 존재하고 재사용할 수 없어 xx 라는 이름을 포기하고 xxxUtil 로 변경해야 합니다……PBL 의 패키지에는 프라이빗 스코프가 없기 때문에 각 패키지는 public 입니다 (패키지 간 메서드 호출은 매우 일반적인 일이며, 각 패키지는 다른 패키지에 대해 접근 가능합니다)

PBF 의 경우, 小张의 xxUtil 은 자연스럽게 featureA 에 배치되고, 小李의 xxUtil 은 featureB 에 배치됩니다. util 이 공통적인 것 같다면 util 패키지를 확인하여 유틸리티 메서드를 xxUtil 에 추가해야 하는지 검토하고, class 명명 충돌이 없어집니다

PBF 의 패키지에는 프라이빗 스코프가 있으며, featureA 는 featureA 하위의 어떤 것도 접근해서는 안 됩니다 (접근해야 한다면 인터페이스 정의에 문제가 있다는 것을 의미합니다)

  • 기능을 삭제하는 것이 매우 쉬움

통계를 통해 새 기능을 아무도 사용하지 않는다는 것을 알게 되어, 이번 버전에서 그 기능을 제거해야 합니다

PBL 의 경우, 기능의 진입점에서 전체 비즈니스 프로세스까지 영향을 받는 모든 코드와 class 를 찾아서 삭제해야 하며, 조금만 부주의하면 실패합니다

PBF 의 경우, 간단합니다. 먼저 해당 패키지를 삭제하고, 기능의 진입점을 삭제합니다 (패키지를 삭제하면 오류가 발생합니다), 완료입니다

  • 높은 추상화

문제를 해결하는 일반적인 방법은 추상에서 구체로이며, PBF 패키지 이름은 기능 모듈에 대한 추상화이고, 패키지 내의 class 는 구현 세부 사항으로, 추상에서 구체로 부합하지만, PBL 은 반대입니다

PBF 는 AppName 확정에서 시작하여 기능 모듈에 따라 패키지를 분할하고, 각 부분의 구현 세부 사항을 고려하지만, PBL 은 처음부터 dao 계층이 필요한지, com 계층이 필요한지 등을 고려해야 합니다

  • class 만을 통해 로직 코드를 분리

PBL 은 class 와 패키지 모두를 분리하지만, PBF 는 class 만을 통해 로직 코드를 분리합니다

패키지를 통해 분리할 필요는 없습니다. PBL 에서도 어색한 상황이 발생할 수 있습니다:

    ├─service
            │      MainServ.java

PBL 에 따르면, service 패키지 하위의 모든 것은 Controller 이므로 Serv 접미사가 필요하지 않아야 하지만, 실제로는 코딩의 편의를 위해 직접 service 패키지를 import 하며, Serv 접미사는 도입된 class 와 현재 패키지 내의 class 명명 충돌을 피하기 위한 것입니다. 물론 접미사를 사용하지 않아도 되지만, 명확한 패키지 경로를 작성해야 합니다. 예를 들어new net.ayqy.service.Main(), 번거롭습니다

PBF 는 매우 편리하며, import 없이 직접new MainServ()라고 작성할 수 있습니다

  • 패키지 크기에 의미가 있음

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 에 따라 명명된 패키지 외에도 layer 와 유사한 패키지도 정의했습니다. 다음과 같습니다:

  • 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 파싱, 단 db 조작 제외) 는 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) 구현, 독립 스레드에서 비동기 작업 수행용
            │
            ├─ui
            │      BaseActivity 구현, 및 커스텀 view 와 widget 구현, 관련 Adapter 도 여기에 배치
            │
            ├─util
            │      툴 클래스 구현, 정적 메서드 제공
            │
            ├─feature1
            │      Item.java                model 정의
            │      ItemHelper.java          modelHelper 구현
            │      feature1Activity.java    UI 정의
            │      feature1DAO.java         프라이빗 db 조작
            │      feature1Utils.java       프라이빗 툴 함수
            │      ...기타 프라이빗 클래스
            │
            ├─...기타 feature

물론, 이러한 코드 구성 방안은Android 뿐만 아니라사용해 보고 좋으면 사용하면 됩니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성