관찰자 모드 해석 및 안드로이드에서의 실제 응용

20743 단어
개술
생활과 실제 프로젝트에서 여러 대상과 자주 관련되어 하나의 특수한 대상의 데이터 변화에 관심이 있고 이 여러 대상은 그 특수한 대상의 데이터 변화를 추적하기를 원한다. 이런 상황에서 관찰자 모델을 사용할 수 있다. 예를 들어 일기예보의 경우 많은 사용자들이 기상청의 일기예보를 자주 받을 수 있다고 믿는다.혹은 잡지의 경우, 구독을 하면 정기적으로 우편물의 배달 등을 받을 수 있다.
관찰자 모델은 여러 대상이 한 대상의 데이터 변화 상황을 알고 싶어 하는 성숙한 모델이다.관찰자 모드에는'주제(관찰됨)'라고 불리는 대상과'관찰자'라고 불리는 몇 개의 대상이 있는데'주제'와'관찰자'사이에는'주제'의 상태가 변할 때 모든'관찰자'가 통지를 받는 일대다 의존 관계가 있다.앞에서 말한'기상국의 일기예보센터'는 관찰자 모델의 구체적인'주제'에 해당한다.문자를 받은 모든 사용자는 관찰자 모델의 구체적인'관찰자'에 해당한다.
관찰자 모드의 구조
관찰자 모드의 구조에는
●주제(Subject): 일반적으로 주제는 하나의 인터페이스이다. 이 인터페이스는 구체적인 주제가 실현되어야 하는 방법을 규정하고 일반적으로 추가, 삭제, 갱신 등 기본적인 방법이 있기 때문에 당연히 적절하게 확대할 수 있다. 일반적인 주제도 피관찰자라고 부른다.
●관찰자(Observer): 관찰자도 일반적으로 하나의 인터페이스이며, 주요 방법은 내용을 업데이트하는 인터페이스 방법이다.
●구체적 주제(ConcreteSubject): 구체적 주제는 주제 인터페이스 클래스를 실현하는 실례로 이 실례는 자주 변화할 수 있는 데이터를 포함한다.구체적인 주제는 ArrayList와 같은 집합을 사용하여 관찰자의 인용을 저장하여 데이터가 변할 때 구체적인 관찰자에게 알려야 한다.
●구체적 관찰자(ConcreteObserver): 구체적 관찰자는 관찰자의 인터페이스 클래스를 실현하는 실례이다.구체적인 관찰자는 구체적인 주제 인용을 저장할 수 있는 주제 인터페이스 변수를 포함하여 구체적인 관찰자가 구체적인 주제에 자신의 인용을 구체적인 주제의 집합에 추가하여 자신을 관찰자로 만들거나 이 구체적인 주제가 자신을 구체적인 주제의 집합에서 삭제하여 자신이 더 이상 관찰자가 되지 않도록 한다.그래서 일반 관찰자의 방법에는 삭제된 인터페이스가 있다.
다음은 관찰자 모드의 분류도입니다.
유형의 구조도와 위에서 소개한 캐릭터가 완전히 일치하는 것을 볼 수 있다.
관찰자 모델의 장점
●구체적 주제와 구체적 관찰자는 느슨한 결합 관계다.주제 인터페이스는 관찰자 인터페이스에만 의존하기 때문에 구체적인 주제는 관찰자가 관찰자 인터페이스를 실현하는 어떤 종류의 실례일 뿐 구체적인 종류가 어떤 종류인지 알 필요는 없다.마찬가지로 관찰자는 주제 인터페이스에만 의존하기 때문에 구체적인 관찰자는 주제가 주제 인터페이스를 실현하는 특정한 유형의 실례라는 것을 알 뿐 구체적인 종류가 어떤 종류인지 알 필요가 없다.
● 관찰자 모델은 디자인 모델의'개-폐 원칙'을 만족시킨다.주제 인터페이스는 관찰자 인터페이스에만 의존하면 구체적인 주제를 만드는 클래스도 관찰자 인터페이스에만 의존하게 할 수 있기 때문에 관찰자 인터페이스를 실현하는 클래스를 추가하면 구체적인 주제를 만드는 클래스 코드를 수정할 필요가 없다.마찬가지로 구체적인 관찰자의 클래스를 만드는 것은 주제 인터페이스에만 의존하고 새로운 실현 주제 인터페이스를 만드는 클래스를 추가하면 구체적인 관찰자 클래스를 만드는 코드를 수정할 필요가 없다.
장면 작업
● 한 대상의 데이터가 업데이트될 때 다른 대상에게 통지해야 하지만 이 대상은 통지된 대상과 긴밀한 결합을 원하지 않는다.
● 한 대상의 데이터가 업데이트될 때, 이 대상은 다른 대상도 각자 자신의 데이터를 업데이트해야 하는데, 이 대상은 구체적으로 얼마나 많은 대상이 데이터를 업데이트해야 하는지 모른다
무미건조한 이론을 한 무더기 말했는데 다음은 간단한 예를 보겠다.
먼저 다음과 같은 주제 및 추상 관찰자를 정의합니다.
public interface Subject {
     void registerObserver(Observer o);
     void removeObserver(Observer o);
     void notifyObservers(float temprature);
}

//      
public interface Observer {
    void update(float temprature);

    void remove(Subject mSubject);//          
}

그리고 주제를 실현하는 클래스와 추상적인 관찰자를 실현하는 클래스를 정의한다.
public class ConcreteSubject implements Subject {
    private static final List observers;
    static {
        observers = new ArrayList<>();
        observers.clear();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        if (observers.indexOf(o) >= 0) {
            observers.remove(o);
        }
    }

    @Override
    public void notifyObservers(float temprature) {
        if (observers.size() <= 0) {
            System.out.println("     ");
            return;
        }
        for (final Observer o : observers) {
            o.update(temprature);
        }
    }
}

            ,    ,           

구체적인 관찰자는 다음과 같다.
public class ConcreteObserver implements Observer {

    @Override
    public void update(float temprature) {
        System.out.println("   :" + temprature);
    }

    @Override
    public void remove(Subject mSubject) {
        mSubject.removeObserver(this);//             
    }
}

다음은 코드 테스트하기;
public class ObserverClient {
    public static void main(String[] args){
        final Subject sb = new ConcreteSubject();
        final Observer observer = new ConcreteObserver();
        sb.registerObserver(observer);
        sb.notifyObservers(21.0f);
        
        observer.remove(sb);
        sb.notifyObservers(23.0f);
    }
}

테스트 코드는 매우 간단하다. 먼저 구체적인 관찰자를 등록한 다음에 주제가 온도를 발표한다. 이어서 구체적인 관찰자는 통지를 받는다. 구체적인 관찰자가 주제를 삭제한 후에 더 이상 통지를 받지 못한다. 예를 들어 네가 잡지의 구독을 취소하면 더 이상 잡지를 받지 못한다. 똑같은 이해를 하면 된다. 테스트 결과는 다음과 같다.
이것은 단지 하나의 주제와 하나의 추상적인 관찰자일 뿐이다. 실제로 몇 개의 주제나 추상적인 관찰자든지 모두 똑같다. 기본 원리는 모두'구독-발표-갱신'시스템일 뿐이다.
관찰자 모드가 안드로이드 소스에서의 응용
실제로 안드로이드 시스템의 원본 코드에서 관찰자 모델은 광범위하게 응용된다고 할 수 있다. 우리가 익숙한 어댑터에서 데이터의 업데이트 메커니즘을 간단하게 분석하면 일반적인 상황에서 데이터가 바뀔 때 대부분의 사람들은 한마디로 문제를 해결하는 것을 좋아한다.
mAdapter.notifyDataSetChanged();
           :
public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

이 mObservable는 AdapterDataObservable 대상이고 코드는 다음과 같다.
private final AdapterDataObservable mObservable = new AdapterDataObservable();

다음과 같은 mObservable 업데이트 방법이 다시 호출되었습니다.
public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

여기서 중점을 두고 모든 관찰자를 꺼낸 다음에 onChanged() 방법을 사용하는데 문제가 있다. 언제 구체적인 관찰자를 추가했을까?는 setadapter() 메서드에서 다음과 같은 코드를 사용합니다.
public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }

이어서 또 호출했다
 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
        //         
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
        //     ,  
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        markKnownViewsInvalid();
    }

등록할 때 mAdapter의registerAdapterDataObserver 방법을 호출하는 것을 볼 수 있습니다. 코드는 다음과 같습니다.
public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }
    
public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }
                

다음은 진정으로 새로 고친 곳을 분석한다.코드는 다음과 같습니다.
static class AdapterDataObservable extends Observable {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
//      

차례대로 업데이트를 꺼내서 onChanged () 방법을 호출했습니다. AdapterDataObserver는 추상적인 클래스입니다. 진정한 실현 클래스는 Recycler ViewDataObserver입니다. 코드는 다음과 같습니다.
private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;

            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();//      ,      
            }
        }

드디어 진짜 업데이트된 곳을 볼 수 있다. 바로 이곳의 RequestLayout() 방법. View의 원리에 따르면 RequestLayout은 기본적으로 아버지류의 RequestLayout()에 한 층 한 층 호출되고 최종적으로 맨 윗부분 클래스의 리셋을 호출한다. 마지막에 유명한 ViewRootImpl의 performTraversals() 방법으로 인터페이스를 리셋한다.새로 고침 프로세스가 종료됩니다.
관찰자 모드가 안드로이드 실제에서의 응용
앞에서 말했듯이 관찰자 모델은 한 쌍이 많고 한 대상의 데이터가 바뀌어 다른 대상의 데이터가 동시에 바뀔 때 특히 유용하다. 안드로이드 프로젝트에서 이런 수요가 매우 많다. 예를 들어 방송, 방송이 나간 후에 누가 방송에 등록하면 방송의 소식을 받을 수 있다. 예를 들어 Event Bus 이벤트 버스,메시지 이벤트를 보내면 어디에 있든 누가 메시지 이벤트를 등록하면 동시에 이벤트를 받아서 논리적으로 처리한다. 실제 프로젝트에서 서로 다른 인터페이스에 부딪히기도 하고 UI를 동시에 바꿔야 하는 경우도 있다. 예를 들어 B인터페이스에서 논리를 처리한 다음에 앞에 있는 A인터페이스에서 UI가 바뀌어야 하는 등이다.만약에 아래 두 개의 인터페이스가 있다면 각각 A인터페이스와 B인터페이스이다.
자, 이제 수요가 왔습니다. TestActivity2의 모든 텍스트 변경 버튼을 눌러야 합니다. 그리고TestActivity2와TestActivity1의 TextView는 미리 설정된 텍스트로 바꿔야 합니다. 이 예는 실제 예에서 유래한 것입니다. 물론 실제 예는 빨간색 점이나 다른 UI의 변화와 관련되어 있기 때문에 추출하여 분석하기 어렵습니다.그래서 이 간단한 예를 들어 말하지만 원리는 변하지 않는다. 우리가 먼저 어떻게 실현하는 것이 좋은지 분석해 보자. 물론 이벤트버스가 메시지를 보내면 문제를 해결할 수 있지만 메시지를 보낸 후에 하나하나 수동으로 새로운 텍스트를 설정해야 한다. 여기서 우리는 이벤트버스를 보내지 않고 관찰자 모델을 이용하여 해결한다."""전체 텍스트 변경""을 클릭하면 UI 변경 사항이 무더기로 발생하는 것을 알았습니다. 먼저 다음과 같은 방법으로 텍스트를 업데이트하는 방법을 정의했습니다."
public interface Observer {
    void updateText(String text);//  TextView   
    void remove(Observable observable);//         
}

그리고 이 TextView는 사용자 정의이고 관찰자 인터페이스를 실현했다. 코드는 다음과 같다.
public class AutoUpdatetextView extends android.support.v7.widget.AppCompatTextView implements Observer {
    public AutoUpdatetextView(Context context) {
        super(context);
    }

    public AutoUpdatetextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoUpdatetextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //                     
    @Override
    public void updateText(String text) {
        if (!TextUtils.isEmpty(text)) {
            setText(text);
        }
    }

    @Override
    public void remove(Observable observable) {
        observable.remove(this);
    }
}

자, 추상적인 관찰자와 구체적인 관찰자는 모두 썼습니다. 추상적인 주제와 구체적인 주제를 쓰겠습니다. 코드는 다음과 같습니다.
        ,       
public interface Observable {
    void addObserver(Observer observer);
    void remove(Observer observer);
    void update(String text);
}

 //     
public class ObservableManager implements Observable {
    private static final List observers;
    private static ObservableManager sInstance;

    private ObservableManager() {

    }

    public static ObservableManager getInstance() {
        if (sInstance == null) {
            synchronized (ObservableManager.class) {
                if (sInstance == null) {
                    sInstance = new ObservableManager();
                }
            }
        }
        return sInstance;
    }

    static {
        observers = new ArrayList<>();
        observers.clear();
    }

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        if (observers.indexOf(observer) > 0) {
            observers.remove(observer);
        }
    }

    @Override
    public void update(String text) {
        if (observers.size() <= 0) {
            Log.d("[app]", "        ");
            return;
        }
        for (Observer observer : observers) {
            observer.updateText(text);
        }

    }

    public void removeAll(List mList) {
        if (mList == null || mList.size() == 0) {
            return;
        }
        Log.d("[app]", "        :" + mList.size());
        for (Observer observer : mList) {
            remove(observer);
        }
        Log.d("[app]", "        :" + observers.size());
    }
}

자, 추상적인 주제와 구체적인 실현도 다 썼습니다. TextView를 구체적인 주제에 추가하려면 어떻게 추가해야 하는지,view라는 나무가 window에 추가된 후에 추가합니다. 추가된 판단이 AutoUpdatetextView인지 확인하려면 추가해야 합니다. 그렇지 않으면 추가하지 마십시오. 이를 위해BaseActivity를 써야 하는데, 여기서 레이아웃은 붙이지 않습니다. 코드는 다음과 같습니다.
public abstract class BaseActivity extends AppCompatActivity {

    protected List mList = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mList.clear();
        setContentView(getResID());
        ViewGroup viewGroup = (ViewGroup) getWindow().getDecorView();
        addUpdateTextView(viewGroup);
    }

    protected abstract int getResID();
    // Observer       
    private void addUpdateTextView(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View childView = viewGroup.getChildAt(i);
            if (childView instanceof ViewGroup) {
                ViewGroup childGroup = (ViewGroup) childView;
                addUpdateTextView(childGroup);
            } else {
                if (childView instanceof Observer) {
                    //            
                    Observer observer = (Observer) childView;
                    ObservableManager.getInstance().addObserver(observer);
                    mList.add(observer);
                }
            }
        }
    }
    // onDestroy          AutoUpdatetextView  ,    Activity      
    @Override
    protected void onDestroy() {
        ObservableManager.getInstance().removeAll(mList);
        super.onDestroy();
    }
}

TestActivity1 코드를 살펴보겠습니다.
public class TestActivity1 extends BaseActivity {
    private TextView text;

    @Override
    protected int getResID() {
        return R.layout.activity_test1;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        text = (TextView) findViewById(R.id.text);
        text.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(TestActivity1.this, TestActivity2.class);
                startActivity(intent);
            }
        });
    }
}
    TestActivity2  

TestActivity2 코드:
public class TestActivity2 extends BaseActivity {
    private TextView update;
    String[] texts = {"    ", "  ,    ", "   ", "Android   ", "ios   ", "WP  ", "PHP  ", "Python  "};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        update = (TextView) findViewById(R.id.update);
        update.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int len = texts.length;
                int result = new Random().nextInt(len);
                Log.d("[app]", "result=" + texts[result]);
                //     ,              ,     ,          
                ObservableManager.getInstance().update(texts[result]);
            }
        });
    }

    @Override
    protected int getResID() {
        return R.layout.activity_test2;
    }

}

다음과 같은 이점을 얻을 수 있습니다.
 ObservableManager.getInstance().update(texts[result]);

이 말은 중점이다. 여기서 텍스트를 업데이트하면 이전에 등록된 모든 텍스트 컨트롤러가 메시지를 받을 수 있음을 의미하며 AutoUpdatetextView의 업데이트 텍스트 인터페이스 방법을 걷는다.
//                     
    @Override
    public void updateText(String text) {
        if (!TextUtils.isEmpty(text)) {
            setText(text);
        }
    }

다음과 같이 실행됩니다.
결과적으로 동적 디스플레이 텍스트는 물론 실제 상황에서 더 많은 논리적 조작을 할 수 있고 편폭에 한정되어 있다. 일일이 보여주지 않고 원리를 이해하는 것이 더욱 중요하다. 오늘의 글은 여기까지 쓰고 읽어 주셔서 감사합니다.

좋은 웹페이지 즐겨찾기