16 January 2017

안드로이드 디자인 패턴

앱을 개발하는 동안 사용 할 수 있는 Java 디자인 패턴에 대해서 소개합니다. 디자인 패턴은 소프트웨어 설계에서 자주 활용되는 구조의 모음이나, 나타나는 다양한 패턴들을 정리한 것입니다. 각 카테고리 별 패턴을 살펴보고 각 패턴이 실질적으로 Android에서 어떻게 적용되는지 살펴보겠습니다.

  • 생성
    • Builder
    • Dependency Injection
    • Singleton
  • 구조
    • Adapter
    • Facade
  • 행위
    • Command
    • Observer
    • Model View Controller
    • Model View ViewModel

1. 생성 패턴

생성 패턴이란 객체 생성에 대해서 다루고 상황에 적절한 객체를 만드는 것입니다. 생성에 관련된 패턴을 사용하면 쉽고 간단하게 객체 생성을 할 수 있습니다.

1) Builder

  • 복잡한 인스턴스를 조립하여 만드는 구조
  • 생성자에 파라미터가 많은 클래스인 경우 빌더 패턴을 사용하여 가독성을 높일 수 있음
  • Android에서는 NotificationCompat.Builder와 같은 클래스를 사용 할 때 Builder 패턴이 나타남
Notification notification =new NotificationCompat.Builder(this)
                                      .setSmallIcon(R.drawable.ic_notification)
                                      .setContentIntent(pendingIntent)
                                      .setTicker(message)
                                      .build();
new AlertDialog.Builder(this)
    .setTitle("Builder Dialog")
    .setMessage("Builder Dialog Message")
    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override public void onClick(DialogInterface dialog, int which) {
        }
    })
    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
        @Override public void onClick(DialogInterface dialog, int which) {
        }
    })
    .show();
  • Builder들은 단계적으로 진행되며 사용자에게 중요한 부분들을 지정 할 수 있음

2) Dependency Injection

  • Dependency Injection은 구성 요소간의 의존 관계가 소스코드 내부가 아닌 외부의 설정파일등을 통해 정의
  • 필요한 모든 것은 이미 존재하기 때문에 가져다 사용하기만 하면 됨
  • Dependency Injection은 새로운 객체를 생성 할 때 필요한 객체를 제공
  • Android에서는 네트워크 클라이언트, 이미지 로더, SharedPreferences와 같이 앱의 다양한 지점에서 동일한 객체에 접근 해야할 때 사용하면 좋음
  • Dagger2는 Android에서 가장 많이 쓰이는 Dependency Injection 프레임워크
  • @Module 클래스 어노테이션을 달고 @Provides 메소드 어노테이션를 사용하여 주입하면 됨
@Module
public class AppModule {
    @Provides
    SharedPreferences provideSharedPreferences(Application app) {
        return app.getSharedPreferences("setting", Context.MODE_PRIVATE);
    }
}
  • Module은 필요한 객체를 생성하고 초기화함
  • 그런 다음 Component 인터페이스를 생성하여 모듈과 주입 할 클래스를 선언 ```java @Component(modules = AppModule.class) interface AppComponent {

}

- `Component`는 종속성이 어디서 오는 것인지(Module)와 어디로 갈 것인지를 연결
- 마지막으로 `@Inject` 어노테이션을 상요하여 필요에 따라 종속성을 요청
```java
@Inject
SharedPreferences sharedPreferences;
  • Dependency Inject을 사용하면 Activity에서 SharedPreferences 객체가 어떻게 생겼는지를 Activity가 알 필요없이 로컬 스토리지를 사용 할 수 있음
  • 자세한 구현 정보는 Dagger2 Users Guide를 참조

3) Singleton

  • 클래스, 생성자가 여러 차례 생성되더라도 실제로 생성되는 객체는 하나
  • 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴
public class SampleSingleton {
    private static SampleSingleton instance = null;

    private SampleSingleton() {

    }

    public static SampleSingleton getInstance() {
        if (instance == null) {
            synchronized(this) {
                if (instance == null) {
                    instance = new SampleSingleton();
                }
            }
        }

        return instance;
    }
}
  • 위 클래스에서 static 메소드 getInstance()안에 생성자를 숨겨서 객체를 한번만 초기화 하도록 구현
  • SampleSingleton.getInstance();를 사용하여 객체에 엑세스
  • 그러면 앱 전체에서 동일한 클래스의 인스턴스를 사용 할 수 있음
  • Singleton은 여러 개체에서 액세스 할 수 있기 때문에 예측하기 어려운 예기치 않은 부작용을 경험할 수 있음

2. 구조 패턴

구조패턴이란 기존에 생성되어 있는 클래스를 새롭게 구현하는 클래스에 맞지 않는 경우에 사용합니다. 작은 클래스의 합성을 통해 더 큰 클래스 구조를 형성하기 위한 패턴입니다.

1) Adapter

  • Android에서 대표적으로 Adapter패턴을 보이는 클래스는 RecyclerView.Adapter
  • RecyclerView.Adapter클래스는 앱의 비지니스 로직(Model)과 RecyclerView를 연결하는 역활
  • RecyclerView.Adapter은 데이터 아이템(Model)에 접근하고 해당 데이터를 이용하여 아이템에 View를 그리는 역활을 수행
public class SampleAdapter extends RecyclerView.Adapter<SampleViewHolder> {
    private List<Test> tests;

    public SampleAdapter() {
        tests = new ArrayList();
    }

    @Override
    public SampleViewHolder onCreateViewHolder(ViewGroup parent, int position) {
        // Create View Holder
    }

    @Override
    public void onBindViewHolder(SampleViewHolder holder) {
        // View Bind
    }

    @Override
    public int getItemCount() {
        return tests.size();
    }

    public void add(Test item) {
        tests.add(item);
    }
}
  • RecyclerViewTest에 대해서 무엇인지 모름
  • SampleAdapterTestRecyclerView을 연결하기 때문에 RecyclerViewTest에 대해서 알 필요가 없음

2) Facade

  • Facade패턴은 다른 인터페이스 세트를 사용하기 쉽게 만드는 하이-레벨 인터페이스를 제공
  • 라이브러리 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소
  • 공통적인 작업에 대해 간편한 메소드들을 제공
  • Activity에서 도서 목록이 필요한 경우 저장소, 캐시 및 API등의 내부 동작을 이해하지 않고 하나의 객체를 사용하여 해당 목록을 요청하는 경우
  • Retrofit은 Facade 패턴을 구현하는데 도움이 되는 REST API 호출 라이브러리
public interface BooksApi {
    @GET("/books")
    void listBooks(Callback<List> callback);
}
  • 클라이언트는 책의 목록을 얻기위해서는 listBooks()만 호출하면 됨
  • Retrofit을 사용하면 InterceptorOkHttpClient를 이용하여 클라이언트가 현재 진행 중인 상황을 알지 못해도 캐싱 동작을 제어 할 수 있음

3. 행위 패턴

  • 객체의 행위를 조직, 관리, 조합하는데 사용하는 패턴
  • 객체들이 다른 객체와 상호작용하는 방식을 규정
  • 각각 다른 객체들과 통신하는 방법과 객체의 책임을 규정하여 복잡한 행위들을 관리 할 수 있도록 함
  • 두 객체 간의 관계에서부터 앱의 전체 아키텍처에까지 영향을 미침

1) Command

  • 요청을 수행하는 객체가 별도의 수신자를 알지 못해도 요청을 실행하는 방식
  • 요청을 별도의 객체로 캡슐화하고 전송
  • EventBus는 Android에 대표적인 Command 패턴을 보여주는 라이브러리

EvnetBus Diagram

  • 발신자는 EventBus의 post() 메소드를 이용하여 요청을 발신하고 수신자는 @Subscribe 어노테이션을 이용하여 수신을 처리
    public class MessageEvent {
    }
    
  • 발신한 메시지를 생성
eventBus.register(this);

@Subscribe()
public void onEvent(MessageEvent event) {
}
  • 수신자 할당
eventBus.post(event);
  • 발신자에서 메시지 발송

2) Observer

  • 객체간의 1:1 의존성을 정의
  • 하나의 객체가 상태를 변경하면 모든 종속된 객체에 자동으로 통지되고 업데이트를 수행
  • API 호출과 같이 불확실한 시간의 작업을 위해서 사용 할 수 있으며 사용자 입력을 처리하는데 사용 할 수 있음
  • RxJava(RxAndroid)를 사용하면 앱 전체에 해당 패컨을 구현 할 수 있음
apiService.getData(someData)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe (/* an Observer */);
  • 값(이벤트)를 방출 할 Observable 객체를 정의
  • Observable들은 한 번 또는 연속적으로 스트림, 값, 이벤트를 방출함
  • Subscriber는 이러한 값을 수신하고 도작한 대로 응답
  • 예를들어, API 호출을 작성하고 서버에서 응답을 처리할 Subscriber를 지정 할 수 있음

3) Model View Controller

  • Model View Controller(MVC)는 현재 여러 플랫폼에서 사용중인 아키텍처 패턴
  • UI로부터 비지니스 로직을 분리하여 서로 영향 없이 쉽게 고칠 수 있는 어플리케이션 개발 가능
  • Android에서는 MVC로 프로젝트를 설정하는것이 쉬움
    • Model : 데이터 클래스, 실제 세계를 ‘모델링’
    • View : 시각적 클래스, 사용자에게 표시되는 모든 항목
    • Controller : Model과 View 사이의 접착제, View를 업데이트하고 사용자 입력을 받아 Model을 변경
  • 가능한 많은 레이아웃과 리소스 로직을 Android XML으로 이동하면 View 레이어를 깔끔하게 유지 할 수 있음
  • 하지만 Activity(Controller)에 너무 많은 소스가 집약되기 때문에 좋은 방법이 아님

4) Model View Persenter

  • MVC 패턴 기반의 변형 패턴
  • MVC 패턴과 다르게 입력을 View에서 처리하며 Presenter에서는 View의 인스턴스를 가지고 1:1 관계를 유지
  • View에서 이벤트가 발생하면 Presenter에게 전달해주고 Presenter는 해당 이벤트에 따른 Model을 조작하고 그 결과를 바인딩을 통해 View에게 통보를 하여 View를 업데이트
  • 또한 View를 추상화하여 따로 View를 구현하지 않아도 UI 로직에 대한 Test가 가능하며 유지보수가 쉬움

5) Model View ViewModel

  • MVC 패턴 기반의 변형 패턴
  • ViewModel 객체는 Model과 View사이의 “접착제”이지만 Controller와 다르게 동작
  • View에 대한 명령을 표시하고 View를 Model에 바인딩
  • Model이 업데이트되면 해당 View는 ViewModel을 통해 자신을 업데이트
  • 마찬가지로 사용자가 View와 상호 작용할 때 ViewModel은 Model을 자동으로 업데이트

4. 참조



blog comments powered by Disqus