设计模式之观察者模式(Observer)

一、观察者模式介绍

       观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一

       个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

        观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动

        通知其他对象,其他对象将相应的作出反应。

        在观察者模式中发生改变的对象称为“观察目标”,而被通知的对象称为“观察者”,

        一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以

       根据需要增加和删除观察者,使得系统更易于扩展。

       观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,

       或者 一些产品的设计思路,

       如:发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、

               源-监听(Source-Listener) 模式等

      

二、观察者模式原理

        观察者模式结构中通常包括: 观察目标和观察者两个继承层次结构。

        观察者模式结构图如下:

                

        观察者模式中包含以下角色:

                1)Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在

                                    一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个

                                    接口,可以增加和删除观察者对象

                2) ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察

                                   者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送

                                   通知。

                3)Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到

                                   主题更改通知时更新自己。

                4)ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到

                                   主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标

                                   对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保

                                   持一致。

        观察者模式用代码表示如下:         

/**
 * 抽象观察者
 */
public interface Observer {

    //为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    public void update();
}

/*******************************************************
 * 具体观察者
 *
 * @author lbf
 *******************************************************/
public class ConcreteObserverOne implements Observer{

    @Override
    public void update() {
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverOne 得到通知!");
    }
}


/*******************************************************
 * 具体观察者
 *
 * @author lbf
 * 
 *******************************************************/
public class ConcreteObserverTwo implements Observer{

    @Override
    public void update() {
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverTwo 得到通知!");
    }
}


/**
 * 抽象被观察者
 */
public interface Subject {

    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}


/*******************************************************
 * 具体被观察者
 *
 * @author lbf
 *
 *******************************************************/
public class ConcreteSubject implements Subject{

    //定义集合,存储所有观察者对象
    private ArrayList<Observer> observers = new ArrayList<>();


    //注册方法,向观察者集合中增加一个观察者
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    //注销方法,用于从观察者集合中删除一个观察者
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    //通知方法
    @Override
    public void notifyObservers() {
        //遍历观察者集合,调用每一个观察者的响应方法
        for (Observer obs : observers) {
            obs.update();
        }
    }

}


//测试
public static void main(String[] args) {
        //创建目标类(被观察者)
        ConcreteSubject subject = new ConcreteSubject();

        //注册观察者类,可以注册多个
        subject.attach(new ConcreteObserverOne());
        subject.attach(new ConcreteObserverTwo());

        //具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
        subject.notifyObservers();
    }

三、观察者模式应用示例

        以一个摇号服务为例,来看下观察者模式的使用;

        摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息。

        示例代码一:不使用设计模式实现

/*******************************************************
 * 模拟一个摇号服务
 * 不使用设计模式实现
 *
 * @author lbf
 * 
 *******************************************************/
public class DrawHouseService {

    //摇号抽签
    public String lots(String uId){
        if(uId.hashCode() % 2 == 0){
            return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
        }else{
            return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";
        }
    }
}


/*******************************************************
 *
 * @author lbf
 * 
 *******************************************************/
public class LotteryResult {

    private String uId; // 用户id
    private String msg; // 摇号信息
    private Date dataTime; // 业务时间

    public LotteryResult(String uId, String result, Date date) {
    }


    //get&set.....
}


/**
 * 开奖服务接口
 */
public interface LotteryService {

    //摇号相关业务
    public LotteryResult lottery(String uId);
}


/*******************************************************
 * 开奖服务实现
 *
 * @author lbf
 *
 *******************************************************/
public class LotteryServiceImpl implements LotteryService{

    //注入摇号服务
    private DrawHouseService houseService = new DrawHouseService();

    @Override
    public LotteryResult lottery(String uId) {
        //摇号
        String result = houseService.lots(uId);

        //发短信
        System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);

        //发送MQ消息
        System.out.println("记录用户摇号结果(MQ), 用户ID:" +  uId + ",摇号结果:" + result);

        return new LotteryResult(uId,result,new Date());
    }
}


//测试
public static void main(String[] args) {
        LotteryService ls = new LotteryServiceImpl();
        String result  = String.valueOf(ls.lottery("1234567887654322"));
        System.out.println(result);
    }

       

       示例代码二:使用观察者模式实现

               使用“观察者模式” 优化示例一的代码,摇号业务中,摇号、发短信、发MQ消息是一

              个顺序调用的过程,但是除了摇号这个核心功能以外, 发短信与记录信息到MQ的操作

              都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码

              的可扩展性和可维护性。流程图如下:

                      

              示例代码如下:

/**
 * 事件监听接口
 * 抽象观察者
 */
public interface EventListener {

    void doEvent(LotteryResult result);
}


/*******************************************************
 * 短信发送事件
 * 具体观察者
 *
 * @author lbf
 * 
 *******************************************************/
public class MessageEventListener implements EventListener{

    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("发送短信通知用户ID为: " + result.getuId() +
                ",您的摇号结果如下: " + result.getMsg());
    }
}



/*******************************************************
 * MQ消息发送事件
 * 具体观察者
 *
 * @author lbf
 * 
 *******************************************************/
public class MQEventListener implements EventListener{

    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("记录用户摇号结果(MQ), 用户ID:" +  result.getuId() +
                ",摇号结果:" + result.getMsg());
    }
}



/*******************************************************
 * 事件处里
 *
 * @author lbf
 * 
 *******************************************************/
public class EventManager {

    public enum EventType{
        MQ,Message
    }

    //监听器集合
    Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

    public EventManager(Enum<EventType>... operations) {
        for (Enum<EventType> operation : operations) {
            this.listeners.put(operation,new ArrayList<>());
        }
    }

    /**
     * 订阅
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void subscribe(Enum<EventType> eventType, EventListener listener){
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    /**
     * 取消订阅
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void unsubscribe(Enum<EventType> eventType,EventListener listener){
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    /**
     * 通知
     * @param eventType 事件类型
     * @param result    结果
     */
    public void notify(Enum<EventType> eventType, LotteryResult result){
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.doEvent(result);
        }
    }
}


/*******************************************************
 * 开奖服务抽象类
 * 抽象被观察者
 *
 * @author lbf
 * 
 *******************************************************/
public abstract class LotteryService {

    //EventManager 包含注册的观察者
    private EventManager eventManager;

    public LotteryService(){
        //设置事件类型
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
        //订阅
        eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());
        eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());
    }

    public LotteryResult lotteryAndMsg(String uId){
        LotteryResult result = lottery(uId);
        //发送通知
        eventManager.notify(EventManager.EventType.Message,result);
        eventManager.notify(EventManager.EventType.MQ,result);

        return result;
    }

    public abstract LotteryResult lottery(String uId);
}




/*******************************************************
 * 开奖服务
 * 具体被观察者
 *
 * @author lbf
 * 
 *******************************************************/
public class LotteryServiceImpl extends LotteryService{

    //注入摇号服务
    private DrawHouseService houseService = new DrawHouseService();

    @Override
    public LotteryResult lottery(String uId) {
        //摇号
        String result = houseService.lots(uId);

        return new LotteryResult(uId,result,new Date());
    }
}


//测试
public class Test {

    public static void main(String[] args) {
        LotteryService ls = new LotteryServiceImpl();
        LotteryResult result  = ls.lotteryAndMsg("1234567887654322");
        System.out.println(result);
    }
}

四、观察者模式总结

1、观察者模式优点

      1)降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。

      2)被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

2、观察者模式缺点

      1)如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时

      2)如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致

           系统崩溃

3、观察者模式适用场景

      1)当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要

           通知商品详情页、购物车等系统改变数量。

      2)一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公

           众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。

      3)需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影

            响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。

      4)微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或

           朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。

      5)需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它

           的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,

           并将所有上下文数据作为方法参数传递给它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值