且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

设计模式系列之四:观察者模式

更新时间:2022-06-27 09:27:33

前言

观察者模式是属于设计模式中的行为型模式,所谓行为型就是指对象的动作发生改变,比如方法以及状态。那么观察者模式是一种什么模式呢?说白了,观察者模式解决的一对多的依赖关系,当一个对象的状态发生改变的时候,其他依赖此对象的对象会得到通知并且做出相应的改变。但从定义上还是很难理解。我们可以从一个简单的例子中更深地去体会观察者模式。

问题背景

某公司的两名职员在主管离开办公室后,一个在看股票,一个在玩游戏。公司有一个前台,该两名职员让前台当主管回来的时候通知他们不让主管看到他们在干其他的事。

编码实践一

根据上述场景,下面是我写出的第一版代码:

//抽象职员类
public abstract class Employee{
    protected String name;
    //通知者对象
    protected Notifyer notifyer;
    public Employee(){}
    public Employee(String name,Notifyer notifyer){
        this.name = name;this.notifyer = notifyer;
    }

    public void setName(String name){this.name = name;}
    public String getName(){return name;}

    //更新自己状态的方法,比如当收到前台的通知的时候应该停止看股票继续工作
    public abstract void update();
}

//看股票的职员
public class StockEmployee extends Employee{
    public StockEmployee(String name,Notifyer notifyer){super(name,notifyer);}

    public void update(){
        System.out.println("收到前台" + notifyer.getName() + "的" + notifyer.getMessage() + "的通知," + getName() + "停止看股票,继续工作!");
    }
}

//玩游戏的职员,与上面的代码类似
public class GameEmployee extends Employee{
    public GameEmployee(String name,Notifyer notifyer){super(name,notifyer);}

    public void update(){
        System.out.println("收到前台" + notifyer.getName() + "的" + notifyer.getMessage() + "的通知," + getName() + "停止玩游戏,继续工作!");
    }
}

//之所以创建一个通知者类主要是由于通知者与职员类执行的功能不同
public abstract class Notifyer{
    //具体的通知内容,比如主管回来了,就是一个具体的通知
    protected String name;
    protected String message;
    public Notifyer(String name){this.name = name;}
    //设置消息的内容
    protected void setMessage(String message){this.message = message;}
    protected String getMessage(){return message;}

    public String getName(){return name;}
    public void SetName(String name){this.name = name;}
    public abstract void notifyObserver();
}

public class Receptionist extends Notifyer{
    private List<Employee> observers = new ArrayList<Employee>();

    public Receptionist(String name){super(name);}
    //添加一个观察者,这里就是一个职员对象
    public void addObserver(Employee emp){observers.add(emp);}
    //移除一个观察者,这样被移除的观察者将收不到前台的通知,自然后果你懂得
    public void removeObserver(Employee emp){observers.remove(emp);}

    public void notifyObserver(){
        for(Employee emp : observers){
            emp.update();
        }
    }
}

//测试代码
public class Test{
    public static void main(String[] args){
        //创建一个前台
        Receptionist rep = om.new Receptionist("笑笑");
        rep.setMessage("主管回来啦");

        Employee stockEmp = om.new StockEmployee("子房",rep);
        Employee gameEmp = om.new GameEmployee("天明",rep);
        Employee gameEmp2 = om.new GameEmployee("少龙",rep);

        rep.addObserver(stockEmp);
        rep.addObserver(gameEmp);

        rep.notifyObserver();
    }
}

最后程序的运行结果如下:

设计模式系列之四:观察者模式

我在程序使用的都是抽象类,抽象类是对类的抽象,类是对对象抽象。但是如果考虑到通知者有可能是完全不相关的对象,使用抽象类就不是很合理了,于是可以换成接口,接口是对类行为的抽象,通知者的核心职责就是告诉观察者具体的通知,其他的都不是最重要的。下面对代码进行进一步的的优化:

编码实践二

//修改抽象通知者对象为一个通知接口
public interface INotify{
    void addObserver(Employee emp);
    void removeObserver(Employee emp);
    void notifyObserver();
    String getName();
    String getMessage();
    void setMessage(String message);
    void setName(String name);
}

//修改前台的代码
public class Receptionist implements INotify{
    private List<Employee> observers = new ArrayList<Employee>();
    private String message;
    private String name;

    public Receptionist(String name,String message){
        this.name = name;
        this.message = message;
    }
    //添加一个观察者,这里就是一个职员对象
    public void addObserver(Employee emp){observers.add(emp);}
    //移除一个观察者,这样被移除的观察者将收不到前台的通知,自然后果你懂得
    public void removeObserver(Employee emp){observers.remove(emp);}

    public String getMessage(){return message;}
    public void setMessage(String message){this.message = message;}
    public String getName(){return name;}
    public void setName(String name){this.name = name;}

    public void notifyObserver(){
        for(Employee emp : observers){
            emp.update();
        }
    }
}

//增加一个新的通知者,比如闹钟(假定主管在10分钟后回来,自然在十分钟后看股票的职员就会收到闹钟的通知了)
public class AlarmClock implements INotify{
    private List<Employee> observers = new ArrayList<Employee>();
    private String message;

    private String name;

    public AlarmClock(String name,String message){
        this.name = name;
        this.message = message;
    }

    //添加一个观察者,这里就是一个职员对象
    public void addObserver(Employee emp){observers.add(emp);}
    //移除一个观察者,这样被移除的观察者将收不到前台的通知,自然后果你懂得
    public void removeObserver(Employee emp){observers.remove(emp);}

    public String getMessage(){return message;}
    public void setMessage(String message){this.message = message;}
    public String getName(){return name;}
    public void setName(String name){this.name = name;}

    public void notifyObserver(){
        for(Employee emp : observers){
            emp.update();
        }
    }
}

//注意在Employee及其派生类中的Notifyer对象要改成INotify对象,这里就不粘上代码了

//下面是测试代码
public class Test{
    public static void main(String[] args){
        //创建一个闹钟
        AlarmClock alarmClock = om.new AlarmClock("闹钟当当", "时间到啦");
        Employee stockEmp = om.new StockEmployee("子房",alarmClock);
        Employee gameEmp = om.new GameEmployee("天明",alarmClock);
        Employee gameEmp2 = om.new GameEmployee("少龙",alarmClock);

        alarmClock.addObserver(stockEmp);
        alarmClock.addObserver(gameEmp);

        alarmClock.notifyObserver();
    }
}

最后的测试结果是:

设计模式系列之四:观察者模式

从以上两个测试结果可以发现,只要Notifyer的message属性发生改变,其所管辖的观察者的动作也发生了更新。注意到在测试代码中没有将第三个对象添加进来,这样当Notifyer的状态发生改变其动作不会发生更新。这样只有前两个对象发生了动作的更新。也就是说Notifyer这个对象的状态发生改变导致了其他两个对象的改变。而观察者模式也正是在这种情况下使用的。当一个对象的状态改变导致其他对象的状态也发生改变,而且不知道有多少具体的对象的状态要发生改变的时候,应该使用观察者模式。观察者模式有两个关键对象Subject(对应上面代码中的Notifyer和INotify对象)和Observer(对应上面的Employee对象)。Subject状态的改变会导致Observer对象的状态的改变一个Subject对象可以吧状态的改变通知给任意数目的观察者对象。一个Subject不需要知道具体的观察者,一个观察者也不需要知道其他观察者是否存在。实际上观察者模式主要是为了解耦(实际上23种设计模式中都体现了这一点,只不过不同的设计模式体现程度不同罢了),使得对象之间不依赖具体而是依赖抽象。这样设计的好处对具体的大修改不会影响其他依赖抽象的类,使得类之间的耦合度降低。

可以发现,在上面我们的代码中,具体的Subject是依赖Observer对象的,这样的依赖会不会影响呢?答案是肯定的。

虽然Subject和Observer都是抽象的,但是难保在系统中会一直存在这样抽象的Observer,所以这样的依赖已经最大程度减少了程序之间的耦合,但在这种情况下还是存在问题的。那么还没有其他的方法解决这种耦合呢?有。我们可以采用一种委托的事件机制完成进一步的解耦。事件委托可以将委托者的方法可以事件中去执行,事件委托仅仅需要的是要执行方法的对象、方法以及参数列表,这样事件委托对象就可以根据这三个参数执行制定对象的指定方法,从直观上理解,有点象代理,但不同于代理,由于在Java中没有现成的事件委托模型可以使用,可以实现一个自己的事件委托处理器。实现之后,我们在Subject中引入这个事件委托处理器,并添加相应的事件响应方法,而Observer对象不需要做任何改变。具体事件委托处理器的代码可以参考我的Github上的源码。

最后对观察者模式做一个简单的总结:

  1. 观察者模式主要适用于一个对象的改变会导致其他对象状态的改变的情况
  2. 使用抽象降低需要关联对象的耦合,不依赖具体而是依赖抽象
  3. 观察者模式的不足在于如果Observer对象的不再存在将对系统产生重大影响