Что такое шаблон наблюдателя?
Шаблон наблюдателя — это шаблон разработки программного обеспечения, который устанавливает зависимость «один ко многим» между объектами. Каждый раз, когда состояние одного из объектов («субъект» или «наблюдаемый») изменяется, все другие объекты («наблюдатели»), которые зависят от него, уведомляются.
Давайте рассмотрим пример пользователей, которые подписались на получение предложений от Envato Market по электронной почте. Пользователи в этом случае являются наблюдателями. В любое время, когда есть предложение от Envato Market, они получают уведомление об этом по электронной почте. Затем каждый пользователь может либо принять участие в предложении, либо решить, что он может быть не заинтересован в нем в данный момент. Пользователь (наблюдатель) также может подписаться на получение предложений от другого рынка электронной коммерции, если он хочет, и может впоследствии полностью отказаться от подписки на получение предложений от любого из них.
Этот шаблон очень похож на шаблон публикации-подписки. Субъект или наблюдаемый публикует уведомление для зависимых наблюдателей, даже не зная, сколько наблюдателей подписались на него или кто они такие — наблюдаемые знают только то, что они должны реализовать интерфейс (мы вскоре к этому вернемся), не беспокоясь о том, какое действие могут выполнить наблюдатели.
Преимущества модели наблюдателя
- Субъект мало что знает о своих наблюдателях. Единственное, что он знает, это то, что наблюдатели реализуют или соглашаются с определенным контрактом или интерфейсом.
- Субъекты могут быть повторно использованы без привлечения их наблюдателей, и то же самое касается и наблюдателей.
- Никакой модификации не сделано к предмету, чтобы приспособить нового наблюдателя Новому наблюдателю нужно просто реализовать интерфейс, о котором субъект знает, и затем зарегистрироваться для него.
- Наблюдатель может быть зарегистрирован в нескольких субъектах, на которых он зарегистрирован.
Все эти преимущества дают вам слабую связь между модулями в вашем коде, что позволяет вам создать гибкий дизайн для вашего приложения. В оставшейся части этой статьи мы рассмотрим, как создать собственную реализацию шаблона Observer, а также будем использовать встроенный Java Observer / Observable API, а также рассмотрим сторонние библиотеки, которые могут предложить такую функциональность. ,
Построение нашего собственного образца наблюдателя
1. Создайте предметный интерфейс
Мы начнем с определения интерфейса, который будут реализовывать субъекты (наблюдаемые).
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()
который, как говорится, уведомляет или отправляет широковещательную рассылку всем зарегистрированным наблюдателям о новом изменении данных. Новые значения для полного имени и возраста также передаются. У этого предмета может быть несколько наблюдателей, но в этом уроке мы создадим только одного наблюдателя. Но сначала давайте создадим интерфейс наблюдателя.
2. Создайте интерфейс наблюдателя
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()
мы хотим обновить виджеты TextView
— mTextViewUserFullName
и mTextViewUserAge
— с помощью нового набора значений данных.
Прямо сейчас у нас есть только один класс наблюдателей, но мы можем легко и просто создавать другие классы, которые хотят быть наблюдателями класса UserDataRepository
. Например, мы могли бы легко иметь SettingsActivity
которая также хочет получать уведомления об изменениях пользовательских данных, став наблюдателем.
Двухтактные модели
В приведенном выше примере мы используем push-модель паттерна наблюдателя. В этой модели субъект уведомляет наблюдателей об изменении, передавая данные, которые изменились. Но в модели извлечения субъект все еще уведомляет наблюдателей, но фактически не передает измененные данные. Затем наблюдатели извлекают нужные им данные после получения уведомления.
Использование API встроенного обозревателя Java
До сих пор мы создали нашу собственную реализацию шаблона Observer, но Java имеет встроенную поддержку Observer / Observable в своем API. В этом разделе мы собираемся использовать это. Этот API упрощает некоторые реализации, как вы увидите.
1. Создайте наблюдаемый
Наш 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()
. Теперь, когда мы создали наш наблюдаемый класс, давайте посмотрим, как настроить наблюдателя также.
2. Создайте Обозреватель
Нашему классу 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 +.
Или вам могут понравиться RxAndroid и RxJava. Узнайте больше о них здесь:
Вывод
В этом руководстве вы узнали о шаблоне 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 на естественном языке