让你的对象知悉现况——观察者模式

观察者模式

让你的对象 知悉现况

定义

定义了对象之间的一对多依赖,这 样一来,当一个对象改变状态时,它的所有依赖者都 会收到通知并自动更新

通俗的理解

打个比方 , 你和甲,乙都在找一份java 的工作 , 然后找到了一个猎头

那么猎头就是 主题

你和 甲, 乙 都是观察者

你们找到猎头的过程就是注册观察者/订阅的动作

突然你觉得,你想自己去找了,不再委托他了,这就是移除观察者的过程

猎头得到岗位通知,会给你们每个人都通知职位更新的信息 , 这就是发布

问题

现在要你设计如上所述的一个程序, 你会怎么设计?

还记得前面所说的策略模式吗?

还记得面向接口编程吗?

首先我们得有观察者,观察者肯定有很多,而且各种不一样,根据策略模式应该怎么设计呢

interface Observer{
    //当主题发生变化时,这个方法被调用
    update();
}

然后我们得有主题,同理

interface Subject{
    //注册观察者
    registerObserver(Observer o);
    //移除观察者
    removeObserver(Observer o);
    //通知所有观察者
    notifyObservers();
}

接下来我们有一个猎头,他应该对应一个具体的主题,并且有一个观察者注册列表,然后就是观察者关心的那些数据了

/**
* @author apdoer
* @version 1.0
* @date 2019/10/10 11:40
*/
public class HeadHunting implements Subject {

   private List observers;
   private BigDecimal salary;
   private String workspace;
   private Long workTime;

   public HeadHunting() {
       observers = new ArrayList();
   }

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

   @Override
   public void removeObserver(Observer observer) {
       if (observers.contains(observer)){
           observers.remove(observer);
       }
   }

   @Override
   public void notifyObservers() {
       for (int i = 0; i < observers.size(); i++) {
           Observer observer = (Observer) observers.get(i);
           observer.update(salary,workspace,workTime);
       }
   }

   /**
    * 职位信息更新,通知观察者
    */
   public void jobInfoChanged(){
       notifyObservers();
   }

   public void setJobInfo(BigDecimal salary,String workspace,Long workTime){
       this.salary = salary;
       this.workspace = workspace;
       this.workTime = workTime;
       jobInfoChanged();
   }

   // other methods
}

最后,具体的观察者肯定不能少了,需要关心的数据,还有具体的主题,当然这里我们肯定不引用猎头Headhunting,而是主题Subject

/**
 * 观察者,求职者
 * @author apdoer
 * @version 1.0
 * @date 2019/10/9 16:55
 */
public class JobSearcher implements Observer, Information {
    private BigDecimal salary;
    private String wordspace;
    private Long workTime;

    private Subject headHunting;

    /**
     *
     * @param headHunting
     */
    public JobSearcher(Subject headHunting ) {
        this.headHunting = headHunting;
        //注册观察者
        headHunting.registerObserver(this);
    }

    @Override
    public void showInfo() {
        System.out.println("JobSearcher{" +
                "salary=" + salary +
                ", wordspace='" + wordspace + '\'' +
                ", workTime=" + workTime +
                ", jobData=" + headHunting +
                '}');
    }

    @Override
    public void update(BigDecimal salary, String workspace, Long workTime) {
        this.salary = salary;
        this.wordspace = workspace;
        this.workTime = workTime;
        showInfo();
    }
}

看起来是可以了

我们这样设计的目的是什么呢?

松耦合

观察者模式提供了一种对象设计,让主题和观察者之间松耦合
  • 关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主 题不需要知道观察者的具体类是谁、做了些什么或其他任何细节
  • 任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现 Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可 以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何 时候删除某些观察者。
  • 有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当 观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里 实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所 有实现了观察者接口的对象
  • 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他 们之间的接口仍被遵守,我们就可以自由地改变他们

设计原则

为了交互对象之间的松耦合设计而努力

前面的思路中有的只是伪代码,我们现在来完善他们

观察者接口

/**
 * 观察者
 * @author apdoer
 * @version 1.0
 * @date 2019/10/9 16:16
 */
public interface Observer {

    /**
     * 当观察的主题发生改变时,通知观察者
     * @param salary 薪水
     * @param workspace 工作地
     * @param workTime 工作时间
     */
    void update(BigDecimal salary,String workspace,Long workTime);
}

每个观察者可能并不需要相同的接收方式 / 信息,所有策略模式又可以用起来了

/**
 *
 * @author apdoer
 * @version 1.0
 * @date 2019/10/9 16:29
 */
public interface Information {

    /**
     * 当观察者需要显示信息时,调用此方法
     */
    void showInfo();
}

你看,有一个求职者并不关心其他的,薪水就够了,所以她只希望猎头给她发送的短信里有薪水

/**
 * 观察者,求职者 第二个
 * @author apdoer
 * @version 1.0
 * @date 2019/10/9 16:55
 */
public class JobSearcher1 implements Observer, Information {
    private BigDecimal salary;
    private String wordspace;
    private Long workTime;

    private Subject headHunting;


    /**
     *
     * @param headHunting
     */
    public JobSearcher1(Subject headHunting ) {
        this.headHunting = headHunting;
        //注册观察者
        headHunting.registerObserver(this);
    }

    @Override
    public void showInfo() {
        System.out.println("JobSearcher{" +
                "salary=" + salary +
                ", jobData=" + headHunting +
                '}');
    }

    @Override
    public void update(BigDecimal salary, String workspace, Long workTime) {
        this.salary = salary;
        this.wordspace = workspace;
        this.workTime = workTime;
        showInfo();
    }
}

最后,我们来测试一下

/**
 * 提供职位
 * @author apdoer
 * @version 1.0
 * @date 2019/10/9 17:08
 */
public class JobProvider {

    public static void main(String[] args) {
        HeadHunting hd = new HeadHunting();
        Observer searcher = new JobSearcher(hd);
        Observer searcher1 = new JobSearcher1(hd);

        //通过这个方法来模拟提供给猎头的职位数据更新
        hd.setJobInfo(new BigDecimal(20000),"深圳",24*7*3600*1000L);
        hd.setJobInfo(new BigDecimal(11000),"杭州",10*5*3600*1000L);
        hd.setJobInfo(new BigDecimal(12000),"长沙",7*5*3600*1000L);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKJZThnS-1570690291355)(79CD731F7EF4493F8310319E1108D7E4)]
可以看到,观察者都通知到了,当然你也可以把数据格式化好看一点,把猎头取个名字,当然了,那就不是这篇文章的重心了

现在是不是对观察者模式有点认识了?

那么我们这样的设计是ok 的吗

其实.jdk有自己的观察者设计

有哪些问题呢?

jdk 观察者模式的实现

Observable类,跟我们的Subject接口作用是类似的,这里列出方法,具体实现,大家可以自行查看

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    //注册观察者
    public synchronized void addObserver(Observer o) {}
    
    //移除观察者
    public synchronized void deleteObserver(Observer o) {}
    
    //通知观察者主题发生了变化
    public void notifyObservers() {}
    
    //通知观察者,但是可以自定义内容
    public void notifyObservers(Object arg) {}
    
    //清空观察者列表
    public synchronized void deleteObservers() {}
    
    //标记订阅的主题发生了变化
    protected synchronized void setChanged() {}
    
    //清除标记
    protected synchronized void clearChanged() {}
    
    //返回订阅主题是否发生变化
    public synchronized boolean hasChanged() {}
    
    //返回观察者的数量
    public synchronized int countObservers() {}
}

Observer接口,跟我们的Observer没什么太大区别

public interface Observer {
    //订阅的主题发生变化,这个方法会被调用,这里可以通过指定参数
    void update(Observable o, Object arg);
}
jdk 观察者模式是怎么运作的呢
  1. 创建观察者,实现Observer接口,跟我们自己的差不多,有注册和移除等方法
  2. 创建主题,这里就只能继承Observerable类了,
  3. 主题变化怎么通知给观察者呢 ?
    • 先调用setChanged()方法标记主题已经发生了变化
    • 然后调用notifyObservers()或者notifyObservers(Object args)其中一个,后者支持将特定数据推送给观察者,即我们没有实现的推的模式

这里为什么需要先标记再通知呢?

其实这里的处理是很有必要的,在实际使用中,可能出现这样的情况

消息的来源可能变化更新非常快,而观察者们不希望所有的信息一改变就马上被通知

比如出了一个薪水8000的职位,观察者并不关心,

这一步处理提供了观察者模式更多的弹性

利用jdk的观察者模式重构刚才的设计

再来一个猎头,因为Observerable类中都定义好了,我们这里的结构很简单

/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/10 14:16
 */
public class JdkHeadHunting extends Observable {

    private BigDecimal salary;
    private String workspace;
    private Long workTime;

    public JdkHeadHunting() {
        // 这里不同在初始化了,父类中有
    }

    public void jobInfoChanged(){
        setChanged();
        //拉的模式获取更新
        notifyObservers();
    }
    
    public void setJobInfo(BigDecimal salary,String workspace,Long workTime){
        this.salary = salary;
        this.workspace = workspace;
        this.workTime = workTime;
        jobInfoChanged();
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public String getWorkspace() {
        return workspace;
    }

    public Long getWorkTime() {
        return workTime;
    }
}
  • 来个找工作的,这里就不演示不同显示需求的求职者了,跟上面同理
/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/10 14:23
 */
public class JdkJobSearcher implements Observer, Information {
    private BigDecimal salary;
    private String wordspace;
    private Long workTime;

    private Observable headHunting;

    public JdkJobSearcher(Observable headHunting) {
        this.headHunting = headHunting;
        headHunting.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof JdkHeadHunting) {
            JdkHeadHunting jdkHeadHunting = (JdkHeadHunting)o;
            this.salary = jdkHeadHunting.getSalary();
            this.wordspace = jdkHeadHunting.getWorkspace();
            this.workTime = jdkHeadHunting.getWorkTime();
            showInfo();
        }
    }
    @Override
    public void showInfo() {
        System.out.println("JdkJobSearcher{" +
                "salary=" + salary +
                ", wordspace='" + wordspace + '\'' +
                ", workTime=" + workTime +
                ", headHunting=" + headHunting +
                '}');
    }
}

测试一下

/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/10 14:29
 */
public class JdkTest {
    public static void main(String[] args) {
        JdkHeadHunting hd = new JdkHeadHunting();
        Observer searcher = new JdkJobSearcher(hd);

        hd.setJobInfo(new BigDecimal(20000),"深圳",24*7*3600*1000L);
        hd.setJobInfo(new BigDecimal(11000),"武汉",10*5*3600*1000L);
        hd.setJobInfo(new BigDecimal(12000),"重庆",7*5*3600*1000L);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2aagXEcK-1570690291356)(17D119B24BD5451EA62B406B6EF395A3)]
通知到了

有没有什么问题呢

我们多提供几个订阅者
修改测试类

先看我们自己实现的

public class JobProvider {

    public static void main(String[] args) {
        HeadHunting hd = new HeadHunting();
        Observer searcher = new JobSearcher(hd);
        Observer searcher1 = new JobSearcher1(hd);
        Observer searcher2 = new JobSearcher2(hd);

        hd.setJobInfo(new BigDecimal(20000),"深圳",24*7*3600*1000L);
        hd.setJobInfo(new BigDecimal(11000),"杭州",10*5*3600*1000L);
        hd.setJobInfo(new BigDecimal(12000),"长沙",7*5*3600*1000L);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxlxiS0x-1570690291356)(26509B62EB1C444688DAD096EEE868C6)]
再看看jdk实现的

/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/10 14:29
 */
public class JdkTest {
    public static void main(String[] args) {
        JdkHeadHunting hd = new JdkHeadHunting();
        Observer searcher = new JdkJobSearcher(hd);
        Observer searcher1 = new JdkJobSearcher1(hd);
        Observer searcher2 = new JdkJobSearcher2(hd);

        hd.setJobInfo(new BigDecimal(20000),"深圳",24*7*3600*1000L);
        hd.setJobInfo(new BigDecimal(11000),"武汉",10*5*3600*1000L);
        hd.setJobInfo(new BigDecimal(12000),"重庆",7*5*3600*1000L);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q58p1JDX-1570690291357)(61D7AC900F65407B8A74B480721E8616)]

看到区别没有,

对了

顺序不一样

会有什么缺点呢
  1. java.uitl.Observable实现了它的notifyObservers()方法,这导致了通知观察者的次 序不同于我们先前的次序。谁也没有错,只是双方选择不同的方式实现罢了 一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错 误的结果
  2. java.util.Observable是一个“类”而不是一个"接 口",它甚至没有实现一个接口。不幸的是,java.util.Observable的实现 有许多问题,限制了它的使用和复用,你必须设计一个类继承它。如果某类想同时 具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。 这限制了Observable的复用潜力
  3. setChanged()方法被保护起来了(被定义成 protected),这意味着:除非你继承自Observable,否则你无法 创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原 则:“多用组合,少用继承”

至于怎么选择,看你自己的需求,反正现在的你自己弄一套观察者模式出来,也不是难事,哈哈

总结

  • 多用组合,少用继承
  • 针对接口编程,不针对实现 编程
  • 为交互对象之间的松耦合设 计而努力

关于设计模式,相关代码已经上传到github 设计模式

欢迎大家加入qq群 859759121 ,大量免费vip学习资源,一起成长,一起进步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值