English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
이 문서는 Android 프로그래밍 디자인 패턴 중 관찰자 패턴을 설명합니다. 여러분께 공유하여 참고하시기 바랍니다. 구체적인 내용은 다음과 같습니다:
1. 소개
관찰자 패턴은 매우 자주 사용되는 패턴으로, 가장 많이 사용되는 곳은 GUI 시스템, 구독-발행 시스템입니다. 이 패턴의 중요한 역할 중 하나는 디커플링이며, 관찰 대상과 관찰자 간의 디커플링을 통해 그들 간의 의존성을 더 작게 하거나, 의존성이 없게 만듭니다. GUI 시스템의 경우, 애플리케이션의 UI는 변화가 많으며, 특히 초기에는 비즈니스의 변경이나 제품의 요구 사항 수정으로 인해 애플리케이션 인터페이스도 자주 변경됩니다. 그러나 비즈니스 로직은 기본적으로 크게 변경되지 않습니다. 이 경우, GUI 시스템은 이러한 상황에 대처할 수 있는 메커니즘을 필요로 하며, UI 레이어와 구체적인 비즈니스 로직 간의 디커플링을 위해 관찰자 패턴이 사용됩니다.
2. 정의
객체 간의 일대다의 의존 관계를 정의하여, 어떤 객체가 상태를 변경하면 그에 의존하는 모든 객체가 알림을 받고 자동으로 업데이트됩니다.
3. 사용 시나리오
관련 행위 시나리오, 주의해야 할 것은 관련 행위는 분할 가능하며, '결합' 관계가 아니라는 점입니다.
이벤트 다중 단계 트리거 시나리오.
시스템 간의 메시지 교환 시나리오, 예를 들어 메시지 큐, 이벤트 bus의 처리 메커니즘.
4. 관찰자 패턴의 UML 클래스 그래프
UML 클래스 그래프:
역할 소개:
주제: 추상 주제, 즉 관찰 대상(Observeable)의 역할, 추상 주제 역할은 모든 관찰자 객체의 참조를 하나의 집합에 저장합니다. 각 주제는 어떤 수의 관찰자를 가질 수 있습니다. 추상 주제는 관찰자 객체를 추가하고 제거할 수 있는 인터페이스를 제공합니다.
ConcreteSubject: 구체적인 주제, 이 역할은 관련 상태를 구체적인 관찰자 객체에 저장하고, 구체적인 주제의 내부 상태가 변경될 때 모든 등록된 관찰자에게 알림을 보냅니다. 구체적인 주제 역할은 또한 구체적인 관찰자(ConcreteObservable) 역할로 알려져 있습니다.
Observer: 추상적인 관찰자, 이 역할은 관찰자의 추상 클래스로, 업데이트 인터페이스를 정의하여 주제의 변경 알림을 받을 때 자신을 업데이트합니다.
ConcreteObserver: 구체적인 관찰자, 이 역할은 추상적인 관찰자 역할이 정의한 업데이트 인터페이스를 구현하여 주제의 상태가 변경될 때 자신의 상태를 업데이트합니다.
5. 간단한 구현
이곳에서 드라마를 추적하는 예제를 들어보겠습니다. 일반적으로 최신 드라마를 놓치지 않기 위해 이 드라마를 구독하거나 관찰합니다. 드라마가 업데이트되면 첫 번째로 알림을 받습니다. 그래서 간단하게 구현해 보겠습니다.
추상적인 관찰자 클래스:
/** * 추상적인 관찰자 클래스는 모든 구체적인 관찰자에 대해 인터페이스를 정의하며, 알림을 받을 때 자신을 업데이트합니다 */ public interface Observer { /** * 업데이트됨 * * @param message 메시지 */ public void update(String message); }
추상적인 관찰자 클래스:
/** * 추상적인 관찰자 클래스 */ public interface Observable { /** * 메시지 전송 * * @param message 내용 */ void push(String message); /** * 구독 * * @param observer 구독자 */ void register(Observer observer); }
특정한 관찰자 클래스:
/** * 특정한 관찰자 클래스이며, 구독자입니다 */ public class User implements Observer { @Override public void update(String message) { System.out.println(name + ", + message + "업데이트됨!"); } // 구독자의 이름 private String name; public User(String name) { this.name = name; } }
특정한 관찰자 클래스:
/** * 특정한 관찰자 클래스이며, 구독하는 프로그램입니다 */ public class Teleplay implements Observable{ private List<Observer> list = new ArrayList<Observer>();//구독자 저장 @Override public void push(String message) { for(Observer observer:list){ observer.update(message); } } @Override public void register(Observer observer) { list.add(observer); } }
구현:
public class Client { public static void main(String[] args) { //관찰 대상, 여기서는 사용자가 구독한 드라마 Teleplay teleplay = new Teleplay(); //관찰자, 여기서는 구독 사용자 User user1 = new User("소명"); User user2 = new User("소광"); User user3 = new User("소라"); //구독 teleplay.register(user1); teleplay.register(user2); teleplay.register(user3); //새 메시지 전달 teleplay.push("xxx 드라마"); } }
결과:
소명, xxx 드라마가 업데이트되었습니다! 소광, xxx 드라마가 업데이트되었습니다! 소라, xxx 드라마가 업데이트되었습니다!
위의 코드에서는 일대다의 메시지 전달을 구현했음을 알 수 있으며, 메시지 전달은 Observer와 Observable과 같은 추상 클래스에 의존하며, User와 Teleplay은 완전히 결합되지 않아, 구독 시스템의 유연성과 확장성을 보장합니다.
6. Android 소스 코드에서의 관찰자 패턴
1、BaseAdapter
BaseAdapter는 많은 사람들이 익숙한 것 같습니다. ListView의 어댑터에서는 이를 상속받습니다. 간단히 분석해 보겠습니다.
BaseAdapter 부분 코드:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { //데이터 셋 관찰자 private final DataSetObservable mDataSetObservable = new DataSetObservable(); public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } /** * 데이터 세트가 변경되었을 때 모든 관찰자에게 알림을 보냅니다. */ public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); } }
mDataSetObservable.notifyChanged() 메서드를 확인해 보세요:
public class DataSetObservable extends Observable<DataSetObserver> { /** * 각 관찰자에 대해 {@link DataSetObserver#onChanged}을 호출합니다. * 데이터 세트의 내용이 변경되었을 때 호출됩니다. 수신자 * 데이터 세트를 다음으로 쿼리할 때 새로운 내용을 얻을 것입니다. */ public void notifyChanged() { synchronized(mObservers) { // onChanged()는 앱에서 구현되었기 때문에, 무엇이든 할 수 있으며, 포함하여 // 자신을 {@link mObservers}에서 제거합니다. - 그리고 이는 다음과 같은 경우 문제를 일으킬 수 있습니다. // ArrayList {@link mObservers}에서 이터레이터가 사용됩니다. // 이런 문제를 피하기 위해, 목록을 역순으로 순회하십시오. for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } } }
mDataSetObservable.notifyChanged()에서 모든观察자를 순회하며 onChanged()를 호출하여 observation자에 어떤 일이 일어났는지 알립니다.
그렇다면 观察자는 어디서 왔을까요? 그것은 setAdapter 메서드입니다. 다음과 같은 코드입니다:
@Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter은 선택 모드 상태를 업데이트합니다. super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver);//观察자 등록 ...... 생략 } }
AdapterDataSetObserver는 ListView의 부모 클래스 AbsListView에 정의되어 있으며, 데이터 셋观察자입니다. 코드:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { @Override public void onChanged() { super.onChanged(); if (mFastScroller != null) { mFastScroller.onSectionsChanged(); } } @Override public void onInvalidated() { super.onInvalidated(); if (mFastScroller != null) { mFastScroller.onSectionsChanged(); } } }
이는 AbsListView의 부모 클래스인 AdapterView의 AdapterDataSetObserver를 상속한 것입니다. 코드는 다음과 같습니다 :
class AdapterDataSetObserver extends DataSetObserver { private Parcelable mInstanceState = null; // 이전에 언급했듯이, Adapter의 notifyDataSetChanged를 호출하면 모든 관찰자의 onChanged 메서드가 호출되며, 핵심 구현은 여기에 있습니다 @Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; // Adapter에서 데이터의 개수를 가져옵니다 mItemCount = getAdapter().getCount(); // 이전에 무효화된 커서가 있었던 경우를 감지합니다. // 새 데이터로 다시 채워졌습니다. if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } rememberSyncState(); } checkFocus(); // ListView, GridView 등 AdapterView 컴포넌트를 재구성합니다 requestLayout(); } // 코드는 생략됩니다. public void clearSavedState() { mInstanceState = null; } }
ListView의 데이터가 변경될 때마다 Adapter의 notifyDataSetChanged 함수를 호출하며, 이 함수는 DataSetObservable의 notifyChanged 함수를 호출하며, 이 함수는 모든 감시자(AdapterDataSetObserver)의 onChanged 메서드를 호출합니다. 이것이 감시자 모델입니다!
7. 요약
점:
감시자와 감시 대상 사이는 추상적인 결합이며, 비즈니스 변화에 대응합니다.
시스템의 유연성과 확장성을 강화합니다.
단점:
감시자 모델을 적용할 때는 개발 효율성과 실행 효율성 문제를 고려해야 합니다. 프로그램에는 하나의 감시자, 여러 개의 감시자가 포함되어 있으며, 개발, 디버깅 등의 내용이 복잡하며, Java에서 메시지 알림은 일반적으로 순차적으로 실행되며, 이 경우 하나의 감시자가 지연되면 전체 실행 효율성에 영향을 미칠 수 있습니다. 이 경우 일반적으로 비동기 구현을 사용합니다.
Android와 관련된 내용에 대해 더 궁금한 독자분들은 본 사이트의 특집 기사를 확인하시기 바랍니다. 《Android 개발 입문 및 고급 튜토리얼》、《Android 디버깅 기술 및 일반 문제 해결 방법 요약》、《Android 기본 구성 요소 사용 방법 요약》、《Android 뷰(View) 기술 요약》、《Android 레이아웃(Layout) 기술 요약》 및 《Android 컨트롤러 사용 방법 요약》
본 문서가 모두 여러분의 Android 프로그램 설계에 도움이 되길 바랍니다.
고지사항: 본 문서의 내용은 인터넷에서 수집되었으며, 저작권자가 모두 소유하고 있습니다. 이 내용은 인터넷 사용자가 자발적으로 기여하고 자체적으로 업로드한 내용이며, 본 사이트는 소유권을 가지지 않으며, 인공적으로 편집된 내용이 아니며, 관련 법적 책임도 부담하지 않습니다. 저작권 위반 내용을 발견하시면, notice#w로 이메일을 보내 주시기 바랍니다.3codebox.com(댓글을 작성할 때, #을 @으로 변경하여 신고하시고, 관련 증거를 제공하시면, 해당 내용이 실제로 저작권 위반인지 확인되면, 본 사이트는 즉시 해당 내용을 삭제합니다。)