서두에
최근 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 뿐만 아니라사용해 보고 좋으면 사용하면 됩니다
아직 댓글이 없습니다