什么是观察者模式?
观察者模式是一种设计模式,它在对象之间建立一对多依赖关系。每当其中一个对象(“主体”或“可观察”)的状态发生变化时,所有其他依赖于它的对象(“观察者”)都会收到通知。
此模式与发布-订阅模式非常相似。主体或可观察对象向相关观察者发布通知,甚至不知道有多少观察者订阅了它,或者他们是谁 ——可观察者只知道他们应该实现一个接口,而不必担心观察者可能会执行什么操作。
观察者模式的优势
- 受试者对其观察者知之甚少。它唯一知道的是,观察者实现或同意某个契约或接口。
- 受试者可以在不涉及观察者的情况下重复使用,观察者也是如此。
- 不对主题进行任何修改以容纳新的观察者。新的观察者只需要实现一个主体知道的接口,然后注册到主体。
- 观察者可以注册到其注册的多个主题。
所有这些优点都为您提供了代码中模块之间的松散耦合,从而使您能够为应用程序构建灵活的设计。在本文的其余部分,我们将介绍如何创建自己的观察者模式实现,我们还将使用内置的Java Observer/可观察API,并研究可以提供此类功能的第三方库。
建立我们自己的观察者模式
1. 创建主题界面
我们首先定义一个主体(可观察量)将实现的接口。
`public` `interface` `Subject {`
`void` `registerObserver(RepositoryObserver repositoryObserver);`
`void` `removeObserver(RepositoryObserver repositoryObserver);`
`void` `notifyObservers();`
`}`
在上面的代码中,我们创建了一个包含三种方法的接口。第一种方法,正如它所说,将向主体注册一个类型的观察者(我们很快就会创建该接口)。 将被要求删除想要停止从主题接收通知的观察者,最后,每当有变化时,将向所有观察者发送广播。现在,让我们创建一个具体的主题类,它将实现我们创建的主题接口:
`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
接口。我们有一个数组列表
,它保存观察器,然后在私有构造函数中创建它。观察者通过添加到数组列表
进行注册,同样,通过从数组列表中
删除来取消注册。
请注意,我们正在模拟网络请求以检索新数据。 一旦调用 setUserData()方法并给定全名和年龄的新值,我们就调用 notifyObservers()方法,正如它所说,该方法通知或发送有关新数据更改的广播给所有注册观察者。还会传递全名和年龄的新值。这个主题可以有多个观察者,但在本教程中,我们将只创建一个观察者。但首先,让我们创建观察者接口。
2. 创建观察者界面
public interface RepositoryObserver {
void onUserDataChanged(String fullname, int age);
}
在上面的代码中,我们创建了具体观察者应该实现的观察者接口。这使我们的代码更加灵活,因为我们编码的是接口而不是具体的实现。一个具体的类不需要知道它可能有许多具体的观察者;它所知道的关于它们的只是它们实现了接口。SubjectRepositoryObserver
现在,让我们创建一个实现此接口的具体类。
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);
}
}
在上面的代码中要注意的第一件事是实现接口 - 因此它必须实现方法。在 Activity 的方法中,我们得到了一个实例,然后对其进行初始化并最终将此观察者注册到该实例。
在该方法中,我们希望停止接收通知,因此我们取消注册接收通知。
在该方法中,我们希望使用新的数据值集更新小部件。
现在我们只有一个观察者类,但是我们可以轻松地创建其他想要成为该类的观察者的类。例如,我们可以很容易地有一个,它希望通过成为观察者来通知用户数据的变化。
推拉模型
在上面的示例中,我们使用观察者模式的推送模型。在此模型中,受试者通过传递更改的数据来通知观察者有关更改的信息。但在拉模型中,受试者仍然会通知观察者,但它实际上并没有传递更改的数据。然后,观察者在收到通知后提取所需的数据。
利用 Java 的内置观察者 API
到目前为止,我们已经创建了自己的观察者模式实现,但Java在其API中内置了观察者/可观察者支持。在本节中,我们将使用它。正如您将看到的,此 API 简化了一些实现。
1. 创建可观察量
我们的主题或可观察的主体现在将扩展超类以成为Observable。这是一个希望被一个或多个观察者观察的类。
`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;`
`}`
`}`
现在我们已经重构了我们的类以使用 Java 可观察 API,让我们看看与以前的版本相比发生了什么变化。首先要注意的是,我们正在扩展一个超类(这意味着这个类不能扩展任何其他类),而不是像我们在上一节中所做的那样实现接口。
我们不再举行观察员会议,而是举行观察员会议。这是在超类中处理的。同样,我们不必担心观察员的注册,删除或通知 - 正在为我们处理所有这些问题。
另一个区别是,在这个课程中,我们采用了拉动风格。我们提醒观察者发生了更改,但观察者需要使用我们在此类中定义的字段 getter 来提取数据。如果要改用 push 样式,则可以使用该方法并将更改的数据传递给对象参数中的观察者。
超类的方法将一个标志设置为 true,指示数据已更改。然后,您可以调用该方法。请注意,如果您在呼叫之前未呼叫,则不会通知旁听者。您可以使用该方法检查此标志的值,并将其清除回 false 与 。现在我们已经创建了可观察类,让我们看看如何设置观察者。
2. 创建观察点
我们的可观察类需要一个相应的 Observer才能有用,所以让我们重构我们来实现接口。
`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``);`
`}`
`}`
在该方法中,我们通过使用超类中的方法将此类作为观察者添加到可观察量中。
在观察者必须实现的方法中,我们检查作为参数接收的是否是我们的实例(请注意,观察者可以订阅不同的可观察量),然后将其转换为该实例并使用字段检索我们想要的值。然后,我们使用这些值来更新视图小部件。
当活动被销毁时,我们不需要从可观察量中获取任何更新,因此我们只需通过调用方法从观察者列表中删除活动即可。
网友评论