白话设计模式之(26):观察者模式——解锁对象交互的高效密码

白话设计模式之(26):观察者模式——解锁对象交互的高效密码

大家好!在技术学习的道路上,我们都在不断探索如何让代码更优雅、更高效。设计模式作为编程领域的宝贵财富,能为我们提供解决复杂问题的巧妙思路。今天,咱们继续深入探索观察者模式,它就像是对象交互之间的“高效密码”,一旦掌握,就能让对象之间的信息传递和协同工作变得顺畅有序。希望通过这篇博客,能和大家一起更全面地理解观察者模式,在实际编程中灵活运用,提升代码的质量和开发效率。

一、写作初衷

在软件开发的过程中,对象之间的交互关系错综复杂,一个对象的状态变化往往需要牵动多个相关对象做出相应调整。如果没有合适的设计模式来管理这种关系,代码很容易变得混乱不堪,难以维护和扩展。观察者模式为我们提供了一种清晰、简洁的解决方案,通过定义对象间的依赖关系和消息传递机制,实现对象间的解耦和高效协作。我希望通过分享这篇博客,能帮助大家深入理解观察者模式的原理、应用场景和实现细节,让大家在面对类似问题时能够轻松应对,编写出更健壮、更易读的代码。

二、观察者模式解析

(一)定义与核心概念

观察者模式定义了对象间一种一对多的依赖关系,当一个对象(被观察者,也叫目标对象)的状态发生改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。打个比方,就像一场演唱会,歌手(被观察者)在舞台上的每一个动作、每一句歌声(状态改变)都被台下的观众(观察者)关注着。一旦歌手有新的表现,观众们就会做出相应的反应,比如欢呼、鼓掌等。在这个模式中,关键的两个角色是:

  1. Subject(目标对象/被观察者):它就像演唱会的主办方,知道有哪些观众(观察者)在关注这场演出,负责管理观众的入场(注册观察者)和退场(删除观察者)。当歌手有新的表演环节(自身状态改变)时,会通过广播(通知)的方式让观众知晓。例如在一个电商系统中,商品就是被观察者,它要管理关注该商品的用户(观察者),并在商品的库存、价格等状态发生变化时通知用户。
  2. Observer(观察者):类似于台下的观众,定义了一个更新接口。当收到被观察者的通知时,会执行这个接口中的方法,进行相应的业务处理。比如观众在听到歌手的精彩表演(收到通知)后,会按照自己的方式欢呼、鼓掌(执行更新方法)。

(二)代码示例

为了让大家更直观地理解,我们以一个简单的在线课堂直播系统为例。在这个系统中,老师(被观察者)在直播过程中发布新的知识点(状态改变)时,观看直播的学生(观察者)会收到通知。

首先定义观察者接口:

// 观察者接口
public interface StudentObserver {
    void update(String teacherName, String newKnowledge);
}

接着创建具体的观察者类:

// 具体观察者类 - 学生
public class Student implements StudentObserver {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void update(String teacherName, String newKnowledge) {
        System.out.println(name + ",老师 " + teacherName + " 发布了新知识点:" + newKnowledge);
    }
}

然后定义目标对象接口:

// 目标对象接口 - 老师接口
public interface TeacherSubject {
    void registerObserver(StudentObserver observer);
    void removeObserver(StudentObserver observer);
    void notifyObservers(String newKnowledge);
}

再创建具体的目标对象类:

// 具体目标对象类 - 老师
import java.util.ArrayList;
import java.util.List;

public class Teacher implements TeacherSubject {
    private String name;
    private List<StudentObserver> observers = new ArrayList<>();

    public Teacher(String name) {
        this.name = name;
    }

    @Override
    public void registerObserver(StudentObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(StudentObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String newKnowledge) {
        for (StudentObserver observer : observers) {
            observer.update(name, newKnowledge);
        }
    }

    // 模拟老师发布新知识点
    public void publishKnowledge(String newKnowledge) {
        System.out.println(name + " 发布了新知识点:" + newKnowledge);
        notifyObservers(newKnowledge);
    }
}

最后在客户端代码中使用观察者模式:

public class OnlineClassSystem {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("李老师");

        Student student1 = new Student("小明");
        Student student2 = new Student("小红");

        teacher.registerObserver(student1);
        teacher.registerObserver(student2);

        teacher.publishKnowledge("设计模式的概念");

        teacher.removeObserver(student2);
        teacher.publishKnowledge("观察者模式的应用场景");
    }
}

在这个示例中,老师(Teacher)是目标对象,维护着一个学生(观察者)列表。当老师发布新知识点(调用publishKnowledge方法)时,会通知所有注册的学生,学生收到通知后会执行update方法进行相应处理。如果某个学生(如小红)退出直播(调用removeObserver方法),那么后续老师发布的知识点就不会通知到她。

(三)应用场景

  1. 图形用户界面(GUI)开发:在GUI编程中,观察者模式广泛应用于组件之间的交互。例如,当用户在文本框中输入内容(文本框状态改变)时,可能会触发其他组件(如按钮、标签等)的状态更新。文本框就是被观察者,其他受影响的组件就是观察者。当文本框的内容发生变化时,会通知相关的观察者组件进行相应的处理,比如根据输入内容启用或禁用按钮,更新标签显示的提示信息等。
  2. 消息订阅与发布系统:这是观察者模式最典型的应用场景之一。像邮件订阅、RSS订阅、即时通讯软件的群组消息通知等。用户订阅了特定的主题或频道(成为观察者),当有新的消息发布(被观察者状态改变)时,系统会自动将消息推送给订阅用户。例如,在一个新闻资讯APP中,用户订阅了不同的新闻类别(如科技、体育、娱乐等),当有新的相关新闻发布时,APP会及时通知订阅该类新闻的用户。
  3. 游戏开发:在游戏中,观察者模式也起着重要作用。比如游戏角色的状态变化(如生命值、魔法值、等级提升等)会影响到游戏中的其他元素。角色就是被观察者,而游戏界面的显示组件、游戏逻辑模块等可以作为观察者。当角色的生命值降低时,会通知界面显示更新血条,通知游戏逻辑判断是否触发游戏失败条件等;当角色等级提升时,会通知界面显示升级特效,通知游戏系统解锁新的技能或关卡等。

(四)观察者模式的特性与要点

  1. 推模型和拉模型
    • 推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,类似广播通信。例如,在一个天气预警系统中,气象站(目标对象)将最新的天气预警信息(如暴雨预警、大风预警等详细内容)直接推送给所有订阅的用户(观察者)。
    • 拉模型:目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取。比如在上述在线课堂直播系统中,老师发布新知识点时只通知学生知识点的简要内容,学生如果想深入了解,需要主动查看老师提供的资料(从目标对象获取数据)。
    • 比较与选择:推模型的优点是观察者能及时获取详细信息,但可能会造成数据冗余,且观察者的update方法可能因数据固定而缺乏通用性;拉模型的优点是灵活性高,观察者可按需获取数据,但可能增加观察者获取数据的复杂度。在实际开发中,应根据具体情况选择合适的模型。
  2. Java中的观察者模式
    • Java的支持:Java在java.util包中提供了Observable类和Observer接口,简化了观察者模式的实现。使用Java的这些功能,我们无需自己定义观察者和目标的接口,也不用手动维护观察者的注册信息。触发通知时,需要先调用setChanged方法,这有助于实现更精确的触发控制。
    • 示例:以在线课堂直播系统为例,使用Java内置功能实现时,老师类继承java.util.Observable,学生类实现Observer接口。在老师发布新知识点时,先调用setChanged方法,再调用notifyObservers方法通知观察者。具体代码如下:
import java.util.Observable;
import java.util.Observer;

// 学生类,实现Observer接口
public class Student implements Observer {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof Teacher) {
            Teacher teacher = (Teacher) o;
            System.out.println(name + ",老师 " + teacher.name + " 发布了新知识点:" + arg);
        }
    }
}

// 老师类,继承Observable
public class Teacher extends Observable {
    String name;

    public Teacher(String name) {
        this.name = name;
    }

    // 模拟老师发布新知识点
    public void publishKnowledge(String newKnowledge) {
        System.out.println(name + " 发布了新知识点:" + newKnowledge);
        setChanged();
        notifyObservers(newKnowledge);
    }
}

public class OnlineClassSystemWithJavaUtil {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("李老师");

        Student student1 = new Student("小明");
        Student student2 = new Student("小红");

        teacher.addObserver(student1);
        teacher.addObserver(student2);

        teacher.publishKnowledge("设计模式的概念");

        teacher.deleteObserver(student2);
        teacher.publishKnowledge("观察者模式的应用场景");
    }
}
  1. 目标和观察者的关系
    • 一对多关系:目标和观察者之间通常是一对多的关系,但也可以是一对一(只有一个观察者)。一个观察者也可以观察多个目标,不过这种情况下需要在观察者的更新方法中区分通知来自哪个目标,可通过扩展update方法传递参数或定义不同回调方法来实现。
    • 单向依赖:观察者依赖于目标,目标不依赖于观察者。目标掌握通知的主动权,观察者被动等待通知。在特殊情况下,目标可以有区别地对待观察者,但这不属于标准的原始观察者模式。
  2. 实现要点
    • 具体的目标实现对象要维护观察者的注册信息,通常用集合来保存。
    • 目标实现对象需要维护引起通知的状态,一般是自身状态,特殊情况也可以是其他对象的状态。
    • 具体的观察者实现对象要能接收目标的通知,接收传递的数据或主动获取数据并处理。
    • 注意触发通知的时机,一般在完成状态维护后触发,避免观察者获取到旧数据,导致状态不一致。
    • 在相互观察的情况下(如A对象观察B对象,B对象也观察A对象),要小心处理,防止出现死循环。
    • 多个观察者接收通知的顺序是不确定的,观察者的功能不应依赖于通知顺序。
  3. 优缺点
    • 优点
      • 抽象耦合与解耦:通过抽象出观察者接口,目标对象只知道观察者接口,而不知道具体的观察者类,实现了目标类和具体观察者类之间的解耦,提高了代码的可维护性和扩展性。
      • 动态联动:可以在运行期间动态控制注册的观察者,从而灵活控制某个动作的联动范围,实现动态联动。例如,在一个游戏中,可以根据玩家的不同操作,动态添加或删除某些观察者,控制游戏中不同元素的联动效果。
      • 支持广播通信:目标对象可以向所有注册的观察者发送通知,实现广播通信。虽然可能存在相互广播造成死循环的问题,但通过合理设计可以避免,并且可以通过添加功能限制广播范围。
    • 缺点
      • 可能引起无谓操作:由于是广播通信,不管观察者是否需要,每个观察者都会被调用update方法。如果观察者不需要执行相应处理,就会造成资源浪费,甚至可能引起误更新,导致错误的操作结果。
  4. 观察者模式的本质:观察者模式的本质是触发联动。当修改目标对象的状态时,会触发相应的通知,循环调用所有注册观察者对象的相应方法,实现动态联动。通过注册和取消注册观察者,可以在程序运行期间动态控制联动的功能,同时目标对象和观察者对象的解耦保证了无论观察者如何变化,目标对象都能正确地联动。
  5. 何时选用观察者模式
    • 当一个抽象模型有两个相互依赖的方面,其中一个方面的操作依赖于另一个方面的状态变化时,可以选用观察者模式,将这两个方面封装成观察者和目标对象,使它们可以独立地改变和复用。
    • 当更改一个对象时,需要连带改变多个其他对象,且不确定具体有多少对象需要改变时,可选用观察者模式,被更改的对象作为目标对象,需要连带修改的对象作为观察者对象。
    • 当一个对象必须通知其他对象,但又希望与被通知对象保持松散耦合时,可选用观察者模式,该对象作为目标对象,被通知对象作为观察者对象。

(五)Swing中的观察者模式及变形示例

  1. Swing中的应用:Java的Swing中广泛应用了观察者模式,例如事件处理。Swing组件是被观察的目标,实现监听器的类就是观察者,监听器接口就是观察者接口。调用addXXXListener方法相当于注册观察者,当组件状态发生改变(如被单击)时,会调用注册的观察者的方法(即监听器的方法)。这为我们处理Swing组件的交互提供了便利,同时也展示了一个观察者观察多个目标对象的实现方式,即不同的目标对象使用不同的观察者接口,接口中的方法也不同,从而在具体实现观察者时能够区分不同目标对象的通知。
  2. 简单变形示例——区别对待观察者:在实际应用中,观察者模式常常需要根据具体需求进行变形。以水质监测系统为例,不同程度的水质污染需要通知不同的人员进行处理。这种情况下,我们可以在目标对象中进行判断,根据不同的状态有选择地通知观察者,而不是像标准模式那样通知所有观察者。通过定义观察者接口,并在具体观察者实现中根据自身职责进行相应处理,实现对不同观察者的区别对待,这体现了观察者模式的灵活性和可扩展性。

五、总结

观察者模式作为一种强大的设计模式,在处理对象间的依赖关系和消息传递方面具有显著的优势。通过合理运用观察者模式,我们可以使代码结构更加清晰,降低耦合度,提高系统的可维护性和扩展性。在实际开发中,要根据具体的业务需求和场景,选择合适的实现方式(如推模型或拉模型),并充分利用Java等编程语言提供的相关功能,同时注意避免观察者模式可能带来的问题。

写作不易,如果这篇文章对你有所帮助,希望大家能关注我的博客,点赞评论支持一下!你的每一个点赞、评论和关注都是对我最大的鼓励,我会持续为大家带来更多设计模式相关的优质内容,咱们下次再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一杯年华@编程空间

原创文章不易,盼您慷慨鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值