【设计模式】2、行为型——观察者模式

观察者模式为对象间建立了一个一对多的依赖关系,一个被观察的对象对应多个观察它的对象,当被观察的对象做出动作时,观察它的多个对象会得到通知,执行相应的逻辑。所以观察者模式最核心的就是观察者和观察目标,以及他们之间的依赖关系。接下来,我们慢慢阐述。

一、应用场景

当一个对象执行一个操作时,想要让一个或多个对象也做出对应的动作,进行一个联动的时候,可以考虑使用这个设计模式。
比如:在我上小学的时候,我们的上课铃都是靠老师手动敲的,老师敲上课铃时,所有的同学都应该去上课了,敲下课铃时,大家又快乐的玩耍。这就是一个典型的观察者和观察目标的关系,在这里,学生作为观察者对象,有许多个,有时候有的同学也会请假有事不在学校,这个数量是不确定的。敲铃的老师作为观察目标对象,所有的同学都会去监听这个老师是否敲了铃铛,当老师敲铃,铃响了才会进入上/下课状态。下面我们用代码来实现打铃上下课的问题。

二、实际应用

为了拓展性,会为学生和老师分别定义一个统一的接口或者抽象类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框架进行注入,减少代码侵入性,读者不妨试试。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容