技术干货儿Android知识

【译】观察者模式-响应式编程 【Android RxJava2】

2017-03-14  本文已影响745人  哦嘿嘿哈哈吼

原文地址:Observer Pattern – Reactive Programming [Android RxJava2] ( What the hell is this ) Part1

动机:

说实话,在我一开始学习Rx的时候我遇到了非常多的问题,我尝试了很多教程,看了很多书籍,最终却没能把Rx使用到我的App中去。大多数教程总会说一些类似于“迭代器模式是基于拉模型的(pull based),而Rx是基于推模型的(push based)”这种使我困惑的话,这对于当时我来说毫无帮助。我想要学习Rx,想要知道它的优势所在,想要知道为什么它能帮助我减少bug、减少书写模板式代码的工作量,但是每次看这些教程我看到的都是拉取和推送的对比或指令式编程和交互式编程的对比,这并不是我想要的。一些文章的作者说这就跟观察者模式一样,然而随之时间的推移,困惑越来越多,学习曲线更加陡峭。接下来的时间我又看了一些教程,然后lambda表达式,函数式编程这些术语出现了,我看到他们使用lambda表达式调用mapfilter等等等等方法,但我仍不知道Rx是什么?我为什么要使用这种编程范式?

简介:

如果你和我一样充满困惑的话,我建议你试着去忘记我下面所列出的所有东西。
Rx
Observable
Observer
Map
Filter
FlatMap
Lymbda
Higher order functions
Iterator or Pull
Imperative
Reactive
Dataflow
Streams
FRP
等等。。。

准备好了的话,我们开始去实现企业级应用的一个功能,那么第一步干什么呢?一开始我不会告诉你任何有关Rx的信息,我打算介绍给你一些在接下来的教程中会经常用到的基础知识。

需求:

我们的客户有一个网站,他想要每当他发布一个教程Tutorial时,所有的订阅者User都会收到一封邮件。

解决方法:

我不会真的实现所有东西,只以一种便于我们掌握概念的方式实现它。
是时候分解我们的需求了:
1、我们有一些订阅者,这意味着我们需要储存这些订阅者的信息。
2、当用户发布一个新帖子时,数据库将会被插入一些行。通俗来讲,当教程发布时我们的数据会改变,当数据改变时我需要通知我的订阅用户。
3、邮件客户端,这不是我们所关注的。

前两项是重点,我需要完成一个东西来实现这两点功能。有很多途径可以实现这两个功能,但我要用最简单的最能传达我思想的一种。

我创建了一个User类,只包含用户的name和E-mail。有些人会想,我们应该有一个isSubscribed字段。但在我看来,这会使代码变得复杂,因为需要循环来确定谁才是订阅者,就像下面这样:

public static class User {
    private String name;
    private String email;
    private boolean isSubscribed;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isSubscribed() {
        return isSubscribed;
    }

    public void setSubscribed(boolean subscribed) {
        isSubscribed = subscribed;
    }
}

如果我使用这个类,那么很可能我发送邮件的代码会写成下面这样:

public static void sendEmail(List<User> userList){
    for (User user : userList) {
            // send email to user
    }
}

很好,那么是时候想一下如何管理User了,在这个例子中我会在内存中初始化一个List来存取信息,当用户点击订阅按钮时,就把User存入这个List。如果是一个真实的App,那么会在数据库中建一张表来存取。

private static List<User> subscribedUsers = new ArrayList<>();

想想一下我们有A、B、C、D四个用户,除了B所有人都订阅了,来看看我们的main方法是什么样的:

public static void main(String[] args){

    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");

    // Now A,C and D click subscribe button

    subscribedUsers.add(A);
    subscribedUsers.add(C);
    subscribedUsers.add(D);
}

现在需求的第一项已经完成了,我们保存了想要收到邮件的用户的信息。是时候考虑一下第二点了,当用户发布一个新的Tutorial时通知User,我创建了一个Tutorial类:

public static class Tutorial{
    private String authorName;
    private String post;

    public String getAuthorName() {
        return authorName;
    }

    public void setAuthorName(String authorName) {
        this.authorName = authorName;
    }

    public String getPost() {
        return post;
    }

    public void setPost(String post) {
        this.post = post;
    }
}

同样的问题,我们需要一个地方保存Tutorials教程,我们还是在内存中初始化一个List来存取:

private static List<Tutorial> publishedTutorials = new ArrayList<>();

接下来,代码要变得复杂了。假如,我已经有了3个教程(Android1,Android2,Android3),当3个教程发布时,所有用户都订阅了,这意味着当我发布Android4 教程时,所有用户都会受到邮件。首先,来看第一部分,添加3个教程,所有用户订阅邮件:

public static void main(String[] args){

    Tutorial android1 = new Tutorial("Hafiz", "........");
    Tutorial android2 = new Tutorial("Hafiz", "........");
    Tutorial android3 = new Tutorial("Hafiz", "........");

    publishedTutorials.add(android1);
    publishedTutorials.add(android2);
    publishedTutorials.add(android3);

    // I have already three tutorials and later user subscribed for email

    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");

    // Now A,C and D click subscribe button

    subscribedUsers.add(A);
    subscribedUsers.add(C);
    subscribedUsers.add(D);
}

接着最重要的第二部,我发布了第4篇教程:

public static void main(String[] args){
    //Ignore the below code up to bold lines start
    Tutorial android1 = new Tutorial("Hafiz 1", "........");
    Tutorial android2 = new Tutorial("Hafiz 2", "........");
    Tutorial android3 = new Tutorial("Hafiz 3", "........");
    publishedTutorials.add(android1);
    publishedTutorials.add(android2);
    publishedTutorials.add(android3);
    // I have already three tutorials and later user subscribed for email
    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");
    // Now A,C and D click subscribe button
    subscribedUsers.add(A);
    subscribedUsers.add(C);
    subscribedUsers.add(D);

    Tutorial android4 = new Tutorial("Hafiz 4", "........");
    publishedTutorials.add(android4);

}

我该怎么确定第二篇或更新的教程确实发布了需要发送邮件了呢?嗯...非常关键的需求,我准备实现一个拉取机制polling,polling意味着我会创建一个计时器每隔一定的时间间隔就去检查一下数据是否有变化,这里我会用一个int对象来标识数据是否发生了变化:

private static int lastCountOfPublishedTutorials = 0;
Tutorial android1 = new Tutorial("Hafiz 1", "........");
Tutorial android2 = new Tutorial("Hafiz 2", "........");
Tutorial android3 = new Tutorial("Hafiz 3", "........");

publishedTutorials.add(android1);
publishedTutorials.add(android2);
publishedTutorials.add(android3);
lastCountOfPublishedTutorials = publishedTutorials.size();
polling();

现在我们需要注意一个关键点,如果数字发生变化,就意味着有东西发生变化,在我们的例子中就是有新教程发布需要发送邮件了,先来看看我怎么实现polling的:

private static void polling(){

    Polling polling = new Polling();
    Timer timer = new Timer();
    timer.schedule(polling, 0,1000);

}

这个方法会在服务器启动时调用,在我们的例子中就是在main方法开始时调用,并始终处于激活状态,每隔1s就去检查一下数据是否有变化:

public static class Polling extends TimerTask{

    @Override
    public void run() {

        if(lastCountOfPublishedTutorials < publishedTutorials.size()){
            lastCountOfPublishedTutorials = publishedTutorials.size();
            sendEmail(subscribedUsers);
        }
        System.out.println("Polling");
    }
}

非常简单,只用检查数字变化,更新数字并发送邮件给所有订阅者就行了。IDE输出如下:

Polling
Email send: A
Email send: C
Email send: D
Polling
Polling
Polling

OK~客户提出的所有需求都完成了!是时候review一下我们的实现方式了,我认为polling非常的差劲,难道不能使用其他的实现方式么?当然可以!现在我们来使用第二种途径去实现这个需求。

首先稍微修改一下代码,这次我仍采用非常基础的方式,没有接口,没有抽象,所有都将是具体的实现。最后我会做一点重构的工作来展示职业软件开发是怎样工作的。
让我们来看一下Tutorial类的修改:

public static class Tutorial{

    private String authorName;
    private String post;
    public Tutorial() {
    }

    public Tutorial(String authorName, String post) {
        this.authorName = authorName;
        this.post = post;
    }


    private static List<Tutorial> publishedTutorials = new ArrayList<>();
    private static List<User> subscribedUsers = new ArrayList<>();

    public static void addSubscribedUser(User user){
        subscribedUsers.add(user);
    }

    public static void publish(Tutorial tutorial){
        publishedTutorials.add(tutorial);
        sendEmail(subscribedUsers);
    }
}

新代码中,Tutorial类会关注发布教程和订阅者,就像你看到的,多了addSubscribedUser方法,接下来看一下main方法,你可以对比一下看看两种实现方式有哪些区别:

public static void main(String[] args){

    Tutorial android1 = new Tutorial("Hafiz 1", "........");
    Tutorial android2 = new Tutorial("Hafiz 2", "........");
    Tutorial android3 = new Tutorial("Hafiz 3", "........");

    Tutorial.publish(android1);
    Tutorial.publish(android2);
    Tutorial.publish(android3);

    // I have already three tutorials and later user subscribed for email

    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");

    // Now A,C and D click subscribe button

    Tutorial.addSubscribedUser(A);
    Tutorial.addSubscribedUser(C);
    Tutorial.addSubscribedUser(D);

    Tutorial android4 = new Tutorial("Hafiz 4", "........");
    Tutorial.publish(android4);

}

现在新的Tutorial类负责发布教程,同时负责管理订阅者,所以我们可以把之前的polling删除了,这是一个非常大的改善,开发者不需要编写通知数据变化的逻辑,lastCountOfPublishedTutorials对象也不再需要了,真是太棒了!看一下IDE输出:

Email send: A
Email send: C
Email send: D

我知道上面的输出不好理解因为程序直接退出了,所以我实现了保持程序运行,永不退出并每隔1s发布一个新Tutorial的逻辑,现在你可以看到邮件是怎么发送给User的了:

Email send: A
Email send: C
Email send: D
Email send: A
Email send: C
Email send: D
Email send: A
Email send: C
Email send: D
…… Never exit

是时候寻找一个更加优秀更加专业的实现方式了!
我们还有其他的实现方式?当然~但是在这之前,我要解释一些术语。
有人知道怎么解释Observable么?
在我的字典里,所有东西都可以被观察(observe)。比方说我的花园里有颗贼老漂亮的树,我可以看到这颗树,所以这意味着这棵树是可被观察的。当我正在观察树的时候来了一场雷暴,我注意到猛烈的风吹落了几片叶子。树是可被观察的(Observable)而我是观察者(Observer)。当我是观察者的时候我可以注意到树所发生的变化。假设我妻子也在我的旁边,但是她并没有观察这棵树,那么当叶子被吹落时我能感觉到树发生了变化而我的妻子却不能,过了一会儿,我的妻子也开始观察这棵树,当树叶又被吹落时,我和我的妻子都会发现树发生变化了,这意味着树作为被观察者通知了观察者有变化发生。
如果使用拉取机制polling会发生什么呢?首先我数一下树上的叶子数量并记录下来,过一秒我再数一下并与前一次的数量比较,如果不一致我就知道发生变化了,但接下来的每一秒我都要重复同样的事情!😆现实中我可做不到这样。
在第一个场景中,被观察者发生变化时通知它的观察者的方式你可以称之为推送机制push(Rx就是推送的方式)。

是时候实现Observer、Observable策略了。
在我们的App中谁是Observable呢?没错!就是Tutorial!那么谁负责Observe呢?User。
现在我要介绍一下在专业软件开发中使用观察者模式所用到的接口。

public interface Observer{
    // New tutorial published
    void notifyMe();
}
public interface Observable{

 void register(Observer observer);

 void unregister(Observer observer);

 // New tutorial published to inform all subscribed users
 void notifyAllAboutChange();
}

在我们的应用中用户是观察者所以实现Observe接口,Tutorial是被观察者所以实现Observable接口。

public static class User implements Observer{

    private String name;
    private String email;
    public User() {    }
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    public String getName() {   return name;    }
    public void setName(String name) {        this.name = name;    }
    public String getEmail() {        return email;    }
    public void setEmail(String email) {        this.email = email;    }

    @Override
    public void notifyMe() {
        sendEmail(this);
    }
}

现在当Tutorial发生变化时就可以调用User的notifyMe方法通知User了。

public static class Tutorial implements Observable{

    private String authorName;
    private String post;
    private Tutorial() {};
    public static Tutorial REGISTER_FOR_SUBSCRIPTION = new Tutorial();
    
    public Tutorial(String authorName, String post) {
        this.authorName = authorName;
        this.post = post;
    }
    
    private static List<Observer> observers = new ArrayList<>();
    @Override
    public void register(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void unregister(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyAllAboutChange() {
        for (Observer observer : observers) {
            observer.notifyMe();
        }
    }

    public void publish(){
        notifyAllAboutChange();
    }
}

Tutorial这个类发生了什么变化呢?首先我将User改为Observer,这样任何对象都可以通过订阅来获得通知,在我们的示例中就是User。接着是创建registerunregister方法,用来管理订阅和取消订阅,我们使用一个公开静态变量REGISTER_FOR_SUBSCRIPTION来做这个事情。然后是notifyAllAboutChange方法,负责通知所有订阅者。最后是publish方法,每次我调用此方法,所有已订阅的观察者都会被调用notifyMe方法。

public static void main(String[] args){

    Tutorial android1 = new Tutorial("Hafiz 1", "........");
    android1.publish();
    Tutorial android2 = new Tutorial("Hafiz 2", "........");
    android2.publish();
    Tutorial android3 = new Tutorial("Hafiz 3", "........");
    android3.publish();

    // I have already three tutorials and later user subscribed for email
    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");

    // Now A,C and D click subscribe button

    Tutorial.REGISTER_FOR_SUBSCRIPTION.register(A);
    Tutorial.REGISTER_FOR_SUBSCRIPTION.register(C);
    Tutorial.REGISTER_FOR_SUBSCRIPTION.register(D);

    Tutorial android4 = new Tutorial("Hafiz 4", "........");
    android4.publish();
}

这简直简单到不需要解释,我觉得现在所有人应该知道Observable和Observer是什么了。这两个术语是最重要的,在Rx中99.9%的时间你只会用到这两个术语。如果你已经清晰的理解了这些,那么你应该会很轻松的掌握Rx范式。你可以看到使用这个设计模式给我们的App带来了很大的好处,现在我要使用Rx库完成最后的一些代码,在此之前我想说点东西:
1、这不是一个很酷的例子,但是我想让你了解什么是Observable, 什么是Observer, 什么是Pull, 什么是Push。
2、我使用RxJava实现的功能只是Rx的优点中很小的一方面。
3、我不会向你解释我接下来所使用的方法,你只需要看看代码,不要有压力,在下一个教程中,你会明白一切。
4、重申一遍,RxJava的好处不止这些,我只是想通过这个例子给我和我的朋友打打基础。

在项目中添加Rx库的依赖,你可以在这里下载
让我们看一下使用Rx库可以移除多少代码,想象一下,你在App的一大堆地方使用了观察者模式,你要写多少烦人的模板代码,但是用了Rx,所有模板代码都不需要。

首先移除ObservableObserver接口。

<code>
public interface Observer{
// New tutorial published
void notifyMe();
}
</code>

<code>
public interface Observable{
void register(Observer observer);
void unregister(Observer observer);
// New tutorial published to inform all subscribed users
void notifyAllAboutChange();
}
</code>

<code>
public static class User implements Observer{
private String name;
private String email;
public User() { }
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public void notifyMe() {
sendEmail(this);
}
}
</code>

<code>
public static class Tutorial implements Observable{
private String authorName;
private String post;
private Tutorial() {};
public static Tutorial REGISTER_FOR_SUBSCRIPTION = new Tutorial();
public Tutorial(String authorName, String post) {
this.authorName = authorName;
this.post = post;
}
private static List<Observer> observers = new ArrayList<>();
@Override
public void register(Observer observer) {
observers.add(observer);
}
@Override
public void unregister(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyAllAboutChange() {
for (Observer observer : observers) {
observer.notifyMe();
}
}
public void publish(){
notifyAllAboutChange();
}
}
</code>

然后我要使用RxJava来实现

public static class User implements Action1{

    private String name;
    private String email;
    public User() {}
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public String getEmail() {return email;}
    public void setEmail(String email) {this.email = email;}

    @Override
    public void call(Object o) {
        sendEmail(this);
    }
}

Action1接口是Rx库中的表示Observers的

public static class Tutorial {

    private String authorName;
    private String post;
    private Tutorial() {}

    public static rx.Observable REGISTER_FOR_SUBSCRIPTION = 
                  rx.Observable.just(new Tutorial());

    public Tutorial(String authorName, String post) {
        this.authorName = authorName;
        this.post = post;
    }
    
    public void publish(){
        REGISTER_FOR_SUBSCRIPTION.publish();
    }

}

如果你和旧的类对比一下的话会发现删除了很多代码,这里我使用Rx将REGISTER_FOR_SUBSCRIPTION转化为Rx Observable。现在任意Observer都可以订阅这个Observable,当Observable发生变化时所有Observer都会自动收到通知。

public static void main(String[] args){
    
    Tutorial android1 = new Tutorial("Hafiz 1", "........");
    android1.publish();
    Tutorial android2 = new Tutorial("Hafiz 2", "........");
    android2.publish();
    Tutorial android3 = new Tutorial("Hafiz 3", "........");
    android3.publish();

    // I have already three tutorials and later user subscribed for email
    User A = new User("A","a@a.com");
    User B = new User("B","b@a.com");
    User C = new User("C","c@a.com");
    User D = new User("D","d@a.com");

    // Now A,C and D click subscribe button


    Tutorial.REGISTER_FOR_SUBSCRIPTION.subscribe(A);
    Tutorial.REGISTER_FOR_SUBSCRIPTION.subscribe(C);
    Tutorial.REGISTER_FOR_SUBSCRIPTION.subscribe(D);

    Tutorial android4 = new Tutorial("Hafiz 4", "........");
    android4.publish();

}

IDE 输出:

Email send: A
Email send: C
Email send: D
总结:

我们目前只是学习了Rx的基础——观察者模式。之前如果我告诉你有8个项目需要实现通知功能,你需要实现8次Observer和Observable接口,编写大量的模板式代码,但是如果使用Rx的话,你只需要调用一下rx.Observable.just()方法就可以让一个对象像Observable一样工作。如果你现在还是感到困惑的话,试着忘记Rx的部分,只需要记住什么是观察者模式就好。在下一篇文章中我会运用今天所学的概念以合适的方法把Rx介绍给你。

上一篇 下一篇

猜你喜欢

热点阅读