设计模式笔记-观察者模式

设计模式

观察者模式


观察者模式指的是一系列对象之间存在一对多的依赖关系,一旦某个对象的状态改变,其余对象就会立刻接收到相应的通知。

抽象

一些对象当作可观察者,而另一些对象当作观察者。为了在文字上做一些区别,可以将可观察者称为主题

解释

  • 一对多的依赖关系不仅仅指一个主题可以有多个观察者,还包括一个观察者可以订阅多个主题

  • 只有当主题的状态改变的时候,并且期望推送通知的时候才会通知观察者。这种期望推送的设计用来避免频繁的推送消息给观察者。

自定义观察者模式

假设有一个书店,顾客可以订阅该书店,如果该书店有新书到了顾客就会收到通知。

这里,书店就是一个主题,顾客就是观察者

为了遵循针对接口编程,而非实现的设计原则,需要先设计两个接口Subject,Observer。其中,Subject具有注册观察者,取消注册观察者,通知观察者的行为。Observer具有利用主题推送的信息来更新自己的行为。

public interface Subject {
    void registerObserver(Observer observer);
    void unregisterObserver(Observer observer);
    void notifyObserver(Observer observer);
}
public interface Observer {
    void update(Object... args);
}

关于Observer#update()方法需要说明的是你可以不使用可变参数列表,这里只是个人爱好。

接下来,需要定义一个书店来实现Subject接口,一个中国消费者来实现Observer接口。

public class BookStore implements Subject {
    private static List<Observer> observerList = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
        if (observerList != null) {
            observerList.add(observer);
        }
    }

    @Override
    public void unregisterObserver(Observer observer) {
        if (observerList != null) {
            if (observerList.size() > 0) {
                observerList.remove(observer);
            }
        }
    }

    @Override
    public void notifyObserver(Observer observer) {
        for (Observer o :
                observerList) {
            o.update("Thinking in Java","100");
        }
    }
}
public class ChineseCustomer implements Observer {
    @Override
    public void update(Object... args) {
        String bookName = (String) args[0];
        String bookPrice = (String) args[1];
        System.out.println("received book.\nThe name is : " + bookName + "\n" + "The price is : " + bookPrice);
    }
}

上述代码存在的问题

  • 我们说一个观察者可以订阅多个主题,但是ChineseCustomer#update()方法限制了这种思想,因为它对任何主题发送过来的信息都用唯一的方式来处理!
    解决的办法就是对主题的具体类型进行判断,不同类型采用不同的解析处理。在此处,可以让BookStore#notifyObserver()把自身传递过来,这样,通过RTTI就可以知道是谁发送的信息了。当然,不同实现的解决方式在形式上有一些区别,我们会在java.util实现的观察者模式中看到官方的处理方式。
  • 另外,上述代码中一旦主题有任何状态变化都会立即推送给观察者,这是极其不合理的!因为消费者不会在乎自己不喜欢的书籍的更新消息,比如:一位小学生通常不会在乎微积分习题册的更新,一位贫困的顾客不会买太贵的书籍。总之,推送要有限度!这种判断是否推送的逻辑在不同的场景下完全不同,可以很简单,也可以很复杂。在完善实现一节我以书籍价格是否小于100为标准来判断是否推送消息给顾客。
  • 还存在的问题是硬编码了企图推送的消息与推送的对象。这些都在完善实现一节进行改进。

测试一下

public static void main(String[] args) {
    Subject subject = new BookStore();
    Observer o = new ChineseCustomer();
    subject.registerObserver(o);
    subject.notifyObserver(o);
}
received book.
The name is : Thinking in Java
The price is : 100

官方的实现

jdk的java.util包中包含了ObserableObserver两个文件。其中Obserable是类,Observer是接口。这个类导致了一些问题,因为使用它的话我们无法遵循针对接口编程的原则。它还导致了一些其他问题,见下分析。

官方的实现思路是:所有主题都继承Obserable,同样可以添加/移除/通知观察者。不过,如果想要把消息通知给观察者,则必须先把主题状态changed设置为true来表明主题改变了。这其实有一些歧义,因为该设计的真实意图是主题已经改变了并且需要通知给观察者观察者的实现与自定义观察者模式相同。

import java.util.Observable;

public class BookStore extends Observable {
    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}
public class ChineseCustomer implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        String subjectName = o.getClass().getName();
        if (subjectName.equals("two.two.BookStore")) {
            String[] info = (String[]) arg;
            System.out.println("Received book that name is : " + info[0] + ",price is : " + info[1]);
        }
    }
}
public static void main(String[] args) {
    /*Observable subject = new BookStore();
    Observer o = new ChineseCustomer();
    subject.addObserver(o);
    subject.notifyObservers();*/

    BookStore subject = new BookStore();
    Observer o = new ChineseCustomer();
    subject.addObserver(o);
    subject.setChanged();
    subject.notifyObservers(new String[]{"C#","50"});
}
Received book that name is : C#,price is : 50

可以看到,必须覆盖Obserable#setChanged(),因为它的访问权限是protected!否则,你永远无法真正地通知到观察者。我十分不解这个访问权限的设置。

但是,总之这种对Observable的扩展导致我们不能针对接口编程了(从注释部分的代码可以看到),即每一次都要实现一个具体的主题,这不利于复用。不过,站在jdk开发者的角度,我大概可以理解它们的设计!因为,他/她们可以说“我又不知道你具体想要哪种实现,你自己决定就好!”。

完善实现

定义一个书籍类来代表书籍信息,以info来代表需要传递的信息。

@Override
public void notifyObserver(Object info) {
    if ((((Book) info).getPrice()) >= 100) {
        return;
    }
    for (Observer o :
            observerList) {
        o.update(info);
    }
}
public static void main(String[] args) {
    Subject subject = new BookStore();
    Observer o = new ChineseCustomer();
    subject.registerObserver(o);
    Book book = new Book("C Primer Plus" ,120.50f);
    subject.notifyObserver(book);
    book = new Book("C++" ,39.90f);
    subject.notifyObserver(book);
}
received book.
The name is : C++
The price is : 39.9

当然,上述实现还是不够完美,需要一些细节上的改善,例如仿照官方对是否推送的实现思路,但是允许针对接口

最后,还要说一下松耦合。观察者设计模式的实现中各个对象之间是松耦合的,因为可观察者观察者都可以独立存在,当然也可以交互。这有利于保障程序的可复用性

发布了14 篇原创文章 · 获赞 0 · 访问量 723

猜你喜欢

转载自blog.csdn.net/qq_38878217/article/details/104737626