Статьи

Шаблоны дизайна Android: шаблон наблюдателя

Шаблон наблюдателя — это шаблон разработки программного обеспечения, который устанавливает зависимость «один ко многим» между объектами. Каждый раз, когда состояние одного из объектов («субъект» или «наблюдаемый») изменяется, все другие объекты («наблюдатели»), которые зависят от него, уведомляются.

Давайте рассмотрим пример пользователей, которые подписались на получение предложений от Envato Market по электронной почте. Пользователи в этом случае являются наблюдателями. В любое время, когда есть предложение от Envato Market, они получают уведомление об этом по электронной почте. Затем каждый пользователь может либо принять участие в предложении, либо решить, что он может быть не заинтересован в нем в данный момент. Пользователь (наблюдатель) также может подписаться на получение предложений от другого рынка электронной коммерции, если он хочет, и может впоследствии полностью отказаться от подписки на получение предложений от любого из них.

Этот шаблон очень похож на шаблон публикации-подписки. Субъект или наблюдаемый публикует уведомление для зависимых наблюдателей, даже не зная, сколько наблюдателей подписались на него или кто они такие — наблюдаемые знают только то, что они должны реализовать интерфейс (мы вскоре к этому вернемся), не беспокоясь о том, какое действие могут выполнить наблюдатели.

  • Субъект мало что знает о своих наблюдателях. Единственное, что он знает, это то, что наблюдатели реализуют или соглашаются с определенным контрактом или интерфейсом.
  • Субъекты могут быть повторно использованы без привлечения их наблюдателей, и то же самое касается и наблюдателей.
  • Никакой модификации не сделано к предмету, чтобы приспособить нового наблюдателя Новому наблюдателю нужно просто реализовать интерфейс, о котором субъект знает, и затем зарегистрироваться для него.
  • Наблюдатель может быть зарегистрирован в нескольких субъектах, на которых он зарегистрирован.

Все эти преимущества дают вам слабую связь между модулями в вашем коде, что позволяет вам создать гибкий дизайн для вашего приложения. В оставшейся части этой статьи мы рассмотрим, как создать собственную реализацию шаблона Observer, а также будем использовать встроенный Java Observer / Observable API, а также рассмотрим сторонние библиотеки, которые могут предложить такую ​​функциональность. ,

Мы начнем с определения интерфейса, который будут реализовывать субъекты (наблюдаемые).

1
2
3
4
5
public interface Subject {
    void registerObserver(RepositoryObserver repositoryObserver);
    void removeObserver(RepositoryObserver repositoryObserver);
    void notifyObservers();
}

В приведенном выше коде мы создали интерфейс Java с тремя методами. Первый метод registerObserver() , как он говорит, зарегистрирует наблюдателя типа RepositoryObserver (мы вскоре создадим этот интерфейс) для субъекта. removeObserver() , чтобы удалить наблюдателя, который хочет прекратить получать уведомления от субъекта, и, наконец, notifyObserver() отправит широковещательную рассылку всем наблюдателям при каждом изменении. Теперь давайте создадим конкретный предметный класс, который будет реализовывать созданный нами предметный интерфейс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import android.os.Handler;
import java.util.ArrayList;
 
public class UserDataRepository implements Subject {
    private String mFullName;
    private int mAge;
    private static UserDataRepository INSTANCE = null;
 
    private ArrayList<RepositoryObserver> mObservers;
 
    private UserDataRepository() {
        mObservers = new ArrayList<>();
        getNewDataFromRemote();
    }
 
    // Simulate network
    private void getNewDataFromRemote() {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                setUserData(«Chike Mgbemena», 101);
            }
        }, 10000);
    }
 
    // Creates a Singleton of the class
    public static UserDataRepository getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new UserDataRepository();
        }
        return INSTANCE;
    }
 
    @Override
    public void registerObserver(RepositoryObserver repositoryObserver) {
        if(!mObservers.contains(repositoryObserver)) {
            mObservers.add(repositoryObserver);
        }
    }
 
    @Override
    public void removeObserver(RepositoryObserver repositoryObserver) {
        if(mObservers.contains(repositoryObserver)) {
            mObservers.remove(repositoryObserver);
        }
    }
 
    @Override
    public void notifyObservers() {
        for (RepositoryObserver observer: mObservers) {
            observer.onUserDataChanged(mFullName, mAge);
        }
    }
 
    public void setUserData(String fullName, int age) {
        mFullName = fullName;
        mAge = age;
        notifyObservers();
    }
}

Класс выше реализует интерфейс Subject . У нас есть ArrayList который содержит наблюдателей, а затем создает его в приватном конструкторе. Наблюдатель регистрируется, будучи добавленным в ArrayList и, аналогично, отменяет регистрацию, будучи удаленным из ArrayList .

Обратите внимание, что мы моделируем сетевой запрос для получения новых данных. После setUserData() метода setUserData() и получения нового значения для полного имени и возраста мы вызываем метод notifyObservers() который, как говорится, уведомляет или отправляет широковещательную рассылку всем зарегистрированным наблюдателям о новом изменении данных. Новые значения для полного имени и возраста также передаются. У этого предмета может быть несколько наблюдателей, но в этом уроке мы создадим только одного наблюдателя. Но сначала давайте создадим интерфейс наблюдателя.

1
2
3
public interface RepositoryObserver {
    void onUserDataChanged(String fullname, int age);
}

В приведенном выше коде мы создали интерфейс наблюдателя, который должны реализовать конкретные наблюдатели. Это позволяет нашему коду быть более гибким, потому что мы кодируем интерфейс вместо конкретной реализации. Конкретный Subject класс не должен знать о многих конкретных наблюдателях, которые он может иметь; все, что он знает о них, — это то, что они реализуют интерфейс RepositoryObserver .

Давайте теперь создадим конкретный класс, который реализует этот интерфейс.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
 
public class UserProfileActivity extends AppCompatActivity implements RepositoryObserver {
    private Subject mUserDataRepository;
    private TextView mTextViewUserFullName;
    private TextView mTextViewUserAge;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_profile);
 
        mTextViewUserAge = (TextView) findViewById(R.id.tv_age);
        mTextViewUserFullName = (TextView) findViewById(R.id.tv_fullname);
 
        mUserDataRepository = UserDataRepository.getInstance();
        mUserDataRepository.registerObserver(this);
    }
 
    @Override
    public void onUserDataChanged(String fullname, int age) {
        mTextViewUserFullName.setText(fullname);
        mTextViewUserAge.setText(age);
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUserDataRepository.removeObserver(this);
    }
}

Первое, на что нужно обратить внимание в приведенном выше коде, это то, что UserProfileActivity реализует интерфейс RepositoryObserver — поэтому он должен реализовывать метод onUserDataChanged() . В onCreate() класса Activity мы получили экземпляр UserDataRepository который затем инициализировали и, наконец, зарегистрировали этого наблюдателя.

В onDestroy() мы хотим прекратить получать уведомления, поэтому мы отменяем регистрацию получения уведомлений.

В onUserDataChanged() мы хотим обновить виджеты TextViewmTextViewUserFullName и mTextViewUserAge — с помощью нового набора значений данных.

Прямо сейчас у нас есть только один класс наблюдателей, но мы можем легко и просто создавать другие классы, которые хотят быть наблюдателями класса UserDataRepository . Например, мы могли бы легко иметь SettingsActivity которая также хочет получать уведомления об изменениях пользовательских данных, став наблюдателем.

В приведенном выше примере мы используем push-модель паттерна наблюдателя. В этой модели субъект уведомляет наблюдателей об изменении, передавая данные, которые изменились. Но в модели извлечения субъект все еще уведомляет наблюдателей, но фактически не передает измененные данные. Затем наблюдатели извлекают нужные им данные после получения уведомления.

До сих пор мы создали нашу собственную реализацию шаблона Observer, но Java имеет встроенную поддержку Observer / Observable в своем API. В этом разделе мы собираемся использовать это. Этот API упрощает некоторые реализации, как вы увидите.

Наш UserDataRepository который является нашим объектом или наблюдаемым — теперь расширит суперкласс java.util.Observable чтобы стать Observable . Это класс, который хочет, чтобы за ним наблюдали один или несколько наблюдателей.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import android.os.Handler;
import java.util.Observable;
 
public class UserDataRepository extends Observable {
    private String mFullName;
    private int mAge;
    private static UserDataRepository INSTANCE = null;
 
    private UserDataRepository() {
        getNewDataFromRemote();
    }
 
    // Returns a single instance of this class, creating it if necessary.
    public static UserDataRepository getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new UserDataRepository();
        }
        return INSTANCE;
    }
 
    // Simulate network
    private void getNewDataFromRemote() {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                setUserData(«Mgbemena Chike», 102);
            }
        }, 10000);
    }
 
    public void setUserData(String fullName, int age) {
        mFullName = fullName;
        mAge = age;
        setChanged();
        notifyObservers();
    }
 
    public String getFullName() {
        return mFullName;
    }
 
    public int getAge() {
        return mAge;
    }
}

Теперь, когда мы реорганизовали наш класс UserDataRepository для использования Java Observable API, давайте посмотрим, что изменилось по сравнению с предыдущей версией. Первое, на что нужно обратить внимание, это то, что мы расширяем суперкласс (это означает, что этот класс не может расширять любой другой класс) и не реализуем интерфейс, как мы это делали в предыдущем разделе.

Мы больше не держим ArrayList наблюдателей; это обрабатывается в суперклассе. Точно так же нам не нужно беспокоиться о регистрации, удалении или уведомлении наблюдателей — java.util.Observable обрабатывает все это для нас.

Другое отличие состоит в том, что в этом классе мы используем стиль вытягивания. Мы предупреждаем наблюдателей о том, что с notifyObservers() произошло изменение, но наблюдателям нужно будет извлечь данные, используя получатели полей, которые мы определили в этом классе. Если вы хотите использовать вместо этого стиль push, то вы можете использовать метод notifyObservers(Object arg) и передавать измененные данные наблюдателям в аргументе объекта.

Метод setChanged() суперкласса устанавливает флаг в true, указывая, что данные изменились. Затем вы можете вызвать метод notifyObservers() . Имейте в setChanged() что если вы не setChanged() перед вызовом notifyObsevers() , наблюдатели не будут уведомлены. Вы можете проверить значение этого флага с помощью метода hasChanged() и очистить его до false с помощью clearChanged() . Теперь, когда мы создали наш наблюдаемый класс, давайте посмотрим, как настроить наблюдателя также.

Нашему классу UserDataRepository необходим соответствующий Observer, чтобы быть полезным, поэтому давайте UserProfileActivity рефакторинг нашей UserProfileActivity для реализации интерфейса java.util.Observer .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import com.chikeandroid.tutsplusobserverpattern.R;
import java.util.Observable;
import java.util.Observer;
 
public class UserProfileActivity extends AppCompatActivity implements Observer {
    private Observable mUserDataRepositoryObservable;
    private TextView mTextViewUserFullName;
    private TextView mTextViewUserAge;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_profile);
 
        mTextViewUserAge = (TextView) findViewById(R.id.tv_age);
        mTextViewUserFullName = (TextView) findViewById(R.id.tv_fullname);
 
        mUserDataRepositoryObservable = UserDataRepository.getInstance();
        mUserDataRepositoryObservable.addObserver(this);
    }
 
    @Override
    public void update(Observable observable, Object o) {
        if (observable instanceof UserDataRepository) {
            UserDataRepository userDataRepository = (UserDataRepository)observable;
            mTextViewUserAge.setText(String.valueOf(userDataRepository.getAge()));
            mTextViewUserFullName.setText(userDataRepository.getFullName());
        }
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUserDataRepositoryObservable.deleteObserver(this);
    }
}

В onCreate() мы добавляем этот класс в качестве наблюдателя в наблюдаемый UserDataRepository с помощью addObserver() в суперклассе java.util.Observable .

В методе update() который должен реализовать наблюдатель, мы проверяем, является ли Observable мы получаем в качестве параметра, экземпляром нашего UserDataRepository (обратите внимание, что наблюдатель может подписаться на разные наблюдаемые), а затем мы приводим его к этому экземпляру и извлекаем значения, которые мы хотим использовать с помощью поля getter. Затем мы используем эти значения для обновления виджетов вида.

Когда активность уничтожена, нам не нужно получать какие-либо обновления из наблюдаемой, поэтому мы просто удалим активность из списка наблюдателей, вызвав метод deleteObserver() .

Если вы не хотите создавать свою собственную реализацию шаблона Observer с нуля или использовать Java Observer API, вы можете использовать некоторые бесплатные библиотеки с открытым исходным кодом, доступные для Android, такие как EventBus Greenrobot. Чтобы узнать больше об этом, ознакомьтесь с моим руководством на Envato Tuts +.

  • Android
    Связь в приложении для Android с EventBus

Или вам могут понравиться RxAndroid и RxJava. Узнайте больше о них здесь:

  • Android
    Начало работы с ReactiveX на Android
    Ашраф Хатхибелагал

В этом руководстве вы узнали о шаблоне Observer в Java: что это такое, о преимуществах его использования, о том, как реализовать собственный, с помощью API Java Observer, а также о некоторых сторонних библиотеках для реализации этого шаблона.

А пока ознакомьтесь с другими нашими курсами и руководствами по языку Java и разработке приложений для Android!

  • Android SDK
    Приложения RxJava 2 для Android: RxBinding и RxLifecycle
    Джессика Торнсби
  • Android SDK
    Практический параллелизм на Android с HaMeR
    Жестяная мегали
  • Android
    Обеспечение высокого качества кода Android с помощью инструментов статического анализа
  • Android SDK
    Создайте интеллектуальное приложение с Google Cloud Speech и API на естественном языке
    Ашраф Хатхибелагал