이 글은 https://www.raywenderlich.com에 소개된 글을 저자의 동의를 얻고 번역한 것임을 알려드립니다.

개발자로서 일하며 행복을 유지하기 위해서는 고객과 사장님도 만족시켜야 하지만, 미래의 나 또한 만족시켜야 한다.  (행복한 닭이 더 맛있는 달걀을 낳는다고 하지 않던가)

미래의 나는 과거의 내가 작성한 코드를 이어받게 될 것이고, 아마 왜 이렇게 코드를 작성했는지 궁금해 할 것이다.  헷갈리는 주석을 왕창 달아 놓는 것보다, 많이 사용되는 디자인 패턴을 적용하여 코드를 읽기 쉽고, 구조화 시켜 놓는게 미래의 나를 위해 훨씬 나은 방법일 것이다.

이 글은 안드로이드 개발을 하며 많이 사용하는 디자인 패턴을 소개 할 것이다. 디자인 패턴이란 일반적인 소프트웨어 개발시 마주 할 수 있는 문제들을 해결 할 수 있는 재사용 가능한 해결책이라고 할 수 있다. 이 글에서는 많은 디자인 패턴을 소개하지는 않을 예정이지만, 시작점으로 좋은 글이 될 것이라고 생각한다.

Getting Started

“동일한 수정을 여러 곳에 적용해야 할 부분이 이 프로젝트에 있나요?” – Future You

미래의 당신은 아마 추측하거나 추론하며 프로젝트의 의존성을 찾는 시간을 최소화하고 싶을 것이다. 그러기 위해 프로젝트는 최대한 재사용 가능하며, 읽기도 쉽고, 이해하기 쉬워야 할 것이다.  하나의 클래스부터 전체 프로젝트에 이르기까지 위 목표를 달성하기 위해, 패턴을 다음의 카테고리로 나눌 수 있다.

  • Creational patterns: how you create objects.
  • Structural patterns: how you compose objects.
  • Behavioral patterns: how you coordinate object interactions.

당신은 아마 각각의 패턴을 지칭하는 이름을 모른채 몇 종류의 패턴들은 사용하고 있을지도 모른다. 이제 각각의 카테고리에 어떤 패턴이 있으며 실제로 Android에 어떻게 구현/적용 할 수 있는지 알아보자.

Creational

  • Builder
  • Dependency Injection
  • Singleton

Structural

  • Adapter
  • Facade

Behavioral

  • Command
  • Observer
  • Model View Controller
  • Model View ViewModel

Creational Patterns

“복잡한 오브젝트가 여러곳에서 필요할 때는 어떻게 하죠?” – Future You

미래의 당신은 위 물음에 “동일한 코드가 필요할 때마다 Copy and Paste하면 되잖아요?” 라는 말을 듣고 싶지는 않을 것이다.  대신 Creational Pattern으로 오브젝트의 생성을 단순하게 유지하고 쉽게 반복 가능하도록 만들 수 있습니다.

Builder

단골 샌드위치 가게의 주문서에 빵의 종류와 재료 그리고 양념을 펜으로 선택한다. 주문서의 이름이 “Build my own”이지만 실제로 샌드위치를 직접 만들지는 않고 입맛에 맞게 각각의 재료들을 선택한 뒤 직원에게 넘겨 줄 뿐이다.

이와 비슷하게 Builder pattern은 Object의 복잡한 생성과정(빵을 익히고 자르고, 재료를 넣고, … )을 Object(샌드위치)에서 분리한 것이다. 이와 같은 방법을 통해 동일한 프로세스로 다른 Object(샌드위치)를 쉽게 만들어 낼 수 있다.

Android의 AlertDialog.Builder와 같은 오브젝트에 Builder pattern이 사용된다.

위의 AlertDialog를 Build하는 과정은 단계적으로 진행되며, AlertDialog의 요소 중 수정하고 싶은 부분만 수정 할 수 있도록 해준다.  AlertDialog.Builder 문서를 보면, Alert을 만들 때 매우 제한적인 요소만 설정 할 수 있다는 것을 알 수 있다.

위 코드는 아래와 같은 Alert을 생성한다.

please_use_the_spicy_mustard

Dependency Injection

Dependency Injection은 빌트인 가구가 갖춰진 아파트로 이사하는 것과 비슷하다. 필요한게 이미 있기 때문에; IKEA 홈페이지에 접속해서 가구를 주문하고 배달을 할 필요가 없다.

소프트웨어 측면에서 보자면, Dependency Injection은 새로운 Object를 생성하는 시점에 필요한 Object들을 제공해준다. 따라서 새로 생성되는 Object는 필요한 Object들을 만들 필요가 없다.

안드로이드에서는 앱의 다양한 Activity 또는 Fragment에서 Network Client나 Image Loader, SharedPreferences와 같은 다양한 Object들에 접근 할 필요가 있다. Dependency Injection을 활용하면 이러한 Object들을 Activity나 Fragment들에 Inject 한 뒤, 필요한 시점에 바로 사용 할 수 있도록 해준다.

Dagger 2는 Google과 Square가 협업하여 개발한 Framework으로, Android에서 가장 유명한 Dependency Injection Framework이다. 클래스에 @Module 어노테이션을 추가한 뒤, @Provides 어노테이션으로 필요한 모듈을 제공하도록 할 수 있다.

위 모듈은 Object 생성과 필요한 설정을 수행한다. 규모가 있는 앱을 위한 Best practice로는, 여러 모듈을 함수로 구분하여 생성 할 수도 있다. Component interface를 만든 뒤, Inject가 필요한 모듈과 클래스를 나열한다.

Component는 dependency들이 어디서 와야(module coming from) 되고 어디로 가야(injection point) 되는지를 판단하여 묶어 주는 역할을 한다.

마지막으로 @Inject 어노테이션을 사용해서 Dependency가 필요한 곳에 삽입 할 수 있다.

* Dagger에 대한 간소화된 개요이기 때문에, 자세한 내용은 Dagger 문서를 참조하기 바란다.

Singleton

Singleton 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 해준다. 모델링하는 Object가 실제로 단 하나의 인스턴스만 갖을 때, Singleton 패턴은 잘 동작한다.

위 ExampleSingleton 클래스는 객체의 생성자를 static 함수인 getInstance() 함수 안으로 숨겨서, 객체가 하나만 생성되도록 보장한다. Singleton 객체에 접근이 필요할 때는 아래와 같이 getInstance() 함수로 접근 할 수 있다.

Singleton은 이해하기 가장 쉬운 패턴 중 하나지만, 오/남용되기 가장 쉬운 패턴이기도 하다.  복수의 객체에서 동일한 Singleton 인스턴스에 접근 할 수 있기 때문에 예상치 못한 Side Effect가 발생하기 쉽고, 원인을 파악하기 어려운 경우도 많다.  따라서 사용하기 전에 패턴을 정확히 이해하고 사용하는 것이 중요하다.

 

Structural Patterns

미래의 당신은 전형적인 작업(typical task)을 수행하는 클래스와 오브젝트들을 익숙한 형태로 정리 할 수 있도록 도와준 Structural Pattern을 의심의 여지없이 고마워 할 것이다. AdapterFacade는 안드로이드에서 가장 많이 사용되는 패턴이다.

Adapter

Apollo 13에서 엔지니어들이 네모난 마개를 ‘Round Hole’에 맞추는 유명한 장면이 있다. 이 장면은 Adapter의 역할을 은유적으로 보여준다. Adapter 패턴은 Class의 Interface를 변환하여 사용자가 예상하는 다른 Class의 Interface에 맞출 수 있도록 해준다.
(By converting the interface of a class into another interface the client expects).

당신 앱의 비즈니스 로직은 제품이 될수도, 사용자 또는 건조틀이 될 수도 있다. 그게 뭐가됐든, RecyclerView는 모든 Android App에서 공통으로 사용되는 ‘Round Hole’ 이다.

RecyclerView는 Star Trek의 에피소드를 하나도 본적이 없기 때문에 Tribble이 뭔지 모른다.  하지만 데이터를 처리하고, ViewHolder에 데이터를 올바르게 전달해주는 Adapter의 역할을 한다.

Facade

Facade 패턴은 인터페이스들의 모음을 사용하기 쉽게 하기 위한 Higher-level 인터페이스를 제공한다. 아래 다이어그램은 이를 더 자세히 설명해준다.

만약 Activity에서 도서 목록이 필요하다면, 내부 저장소나 캐시 그리고 API 클라이언트의 내부 로직에 대한 이해 없이도 요청 할 수 있어야 한다.  이는 Activity와 Fragment의 코드를 깨끗하고 간결하게 유지하는 것을 넘어 Activity에 영향을 주지 않고, API 구현에 대한 변경사항을 적용 할 수 있도록 해준다.

SquareOne의 Retrofit은 Facade 패턴을 구현할 수 있도록 도와주는 Open Source Android 라이브러리다. API data를 제공하는 Interface를 아래와 같이 생성하면,

Client에서는 listBooks()을 호출하는 것 만으로 도서 목록을 얻어 올 수 있다.  RequestInterceptor 또는 OkClient의 설정을 수정(customize)하는 것 만으로 Client의 변경없이 캐시 동작 방식을 제어 할 수 있다.

Behavioral Patterns

Behavioral 패턴은 앱의 기능에 책임을 부여 할 수 있도록 해준다. Behavioral 패턴을 통해 프로젝트의 구조나 설계를 파악하는데 도움을 얻을 수 있다. 이 패턴의 Scope는 두 객체간의 관계에서 부터 전체 어플리케이션의 구조에 이르기까지 다양하게 나뉜다. 다양한 Behavioral 패턴이 하나의 앱에 사용되는게 일반적이다.

Command

인도 음식점에서 ‘Saag Paneer’를 주문했을 때, 음식이 만들어지는 과정은 알 필요가 없다. 종업원에게 주문을 알려주기만 하면 된다.

이와 비슷하게 Command 패턴은 Request를 수신하는 곳에 대한 정보없이 발행 할 수 있도록 해준다.

Greenrobot의 EventBus는 Command 패턴이 아래와 같이 동작할 수 있도록 해주는 Android Framework다.

EventBus-Publish-Subscribe

EventBus의 실제 동작방식은 링크를 참고하기 바란다.

Observer

Observer 패턴은 Objects간의 1 대 多 의존성을 정의 한 뒤, Object의 상태가 변경됐을 때, 이에 의존적인 Object들에게 내용이 전달되고, 자동으로 갱신된다.

Observer 패턴은 활용도가 높아, API Call이나 사용자 입력에 대한 반응과 같이 종료시간을 예측하기 어려운 작업에는 모두 적용 할 수 있다.

RxAndroid Framework은 아래와 같은 방식으로 Observer 패턴을 앱에 적용 할 수 있게 해준다.