观察者模式为对象间建立了一个一对多的依赖关系,一个被观察的对象对应多个观察它的对象,当被观察的对象做出动作时,观察它的多个对象会得到通知,执行相应的逻辑。所以观察者模式最核心的就是观察者和观察目标,以及他们之间的依赖关系。接下来,我们慢慢阐述。
一、应用场景
当一个对象执行一个操作时,想要让一个或多个对象也做出对应的动作,进行一个联动的时候,可以考虑使用这个设计模式。
比如:在我上小学的时候,我们的上课铃都是靠老师手动敲的,老师敲上课铃时,所有的同学都应该去上课了,敲下课铃时,大家又快乐的玩耍。这就是一个典型的观察者和观察目标的关系,在这里,学生作为观察者对象,有许多个,有时候有的同学也会请假有事不在学校,这个数量是不确定的。敲铃的老师作为观察目标对象,所有的同学都会去监听这个老师是否敲了铃铛,当老师敲铃,铃响了才会进入上/下课状态。下面我们用代码来实现打铃上下课的问题。
二、实际应用
为了拓展性,会为学生和老师分别定义一个统一的接口或者抽象类Student和Teacher,Student作为抽象观察者,定义一些公共的方法,一些具体的操作由实现类或者子类实现,如上课下课等。Teacher作为抽象观察目标,当老师在打铃时若要能通知到学生,并且让学生执行相应的逻辑,那么老师肯定需要持有学生的引用,这里我们用CopyOnWriteArrayList来保存所有的学生对象,并提供相应的添加和删除学生的方法,用于报名和请假。
Student.java
/**
* Created by mitch on 2018/1/29.
*/
public interface Student {
void startClass();
void endClass();
}
Teacher.java
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by mitch on 2018/1/29.
*/
abstract class Teacher {
protected List<Student> students = new CopyOnWriteArrayList<Student>();
/**
* 学生报名
*
* @param student
*/
public void addStudent(Student student){
students.add(student);
}
/**
* 由于请假等原因,移除掉不需要听铃声的同学
*
* @param student
*/
public void removeStudent(Student student){
students.remove(student);
}
/**
*打上课铃
*
*/
public abstract void classBeginRing();
/**
* 打下课铃
*/
public abstract void classEndRing();
}
下面是学生和老师的具体实现类,分别实现上课下课的方法:
PrimaryStudent.java
/**
*
* Created by mitch on 2018/1/29.
*/
public class PrimaryStudent implements Student{
private String name;
private String state;
public PrimaryStudent(String name) {
this.name = name;
}
@Override
public void startClass() {
this.state = "上课";
System.out.println(String.format("[%s]同学听到上课铃声,准备认真学习,状态变为[%s]",name,state));
}
@Override
public void endClass() {
this.state = "下课";
System.out.println(String.format("[%s]同学听到下课铃声,嗨起来,状态变为[%s]",name,state));
}
/**
* 重写equals方法,删除的时候以name为准
*
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if(!(o instanceof PrimaryStudent)){
return false;
}
return this.name.equals(((PrimaryStudent) o).getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
RingTeacher.java
/**
* Created by mitch on 2018/1/29.
*/
public class RingTeacher extends Teacher{
private String name;
public RingTeacher(String name) {
this.name = name;
}
@Override
public void classBeginRing() {
System.out.println(String.format("上课啦![%s]老师打上课铃了",name));
for(Student student : students){
student.startClass();
}
}
@Override
public void classEndRing() {
System.out.println(String.format("下课啦![%s]老师打下课铃了",name));
for(Student student : students){
student.endClass();
}
}
}
客户端Main.java
/**
* Created by mitch on 2018/1/29.
*/
public class Main {
public static Teacher teacher = new RingTeacher("小红");
public static void main(String[] args) {
init();
//小明请假了
teacher.removeStudent(new PrimaryStudent("小明"));
//打上课铃
teacher.classBeginRing();
//打下课铃
teacher.classEndRing();
}
/**
*
* 初始化学生
*/
private static void init() {
teacher.addStudent(new PrimaryStudent("小明"));
teacher.addStudent(new PrimaryStudent("张三"));
teacher.addStudent(new PrimaryStudent("李四"));
teacher.addStudent(new PrimaryStudent("王五"));
teacher.addStudent(new PrimaryStudent("赵六"));
teacher.addStudent(new PrimaryStudent("韩梅梅"));
teacher.addStudent(new PrimaryStudent("李雷"));
}
}
最后得到运行结果:
从运行结果可以看出,因为小明请假了,从学生列表中移除,所以只有除了小明以外的所有学生都听到了铃声,并且切换为上课状态。
三、总结
观察者模式是一个非常经典的设计模式,又称作“订阅-发布模式”,它可以完成类似广播的功能,但是又使观察者和观察目标实现了解耦,当新增和删除观察者使可以不用修改观察目标的代码,有良好的扩展性,在一些优秀的开源项目里使用的频率也很高,比如Tomcat的Catalina的生命周期也是使用的观察者模式来监听的。所以,如果有类似对象联动或者广播等场景时,考虑下观察者模式吧。
PS:上述的例子中,学生和老师的实例化,可以用数据库或者xml利用反射或者Spring框架进行注入,减少代码侵入性,读者不妨试试。