观察者(订阅)模式

思考观察者模式

观察者模式是典型的发布订阅模式,当一个东西有变化了,就通知所有订阅他的人

1.观察者模式的本质

观察者模式的本质:触发联动。

观察者模式的本质是解耦主题和观察者之间的关系,实现了对象之间的松耦合。主题对象并不知道具体的观察者对象,它只负责发送通知。观察者对象也不需要知道具体的主题对象,它只需要注册为主题的观察者并等待通知。这样可以提高系统的灵活性和可扩展性,方便添加、删除和修改观察者对象,同时也降低了主题对象和观察者对象之间的耦合度。

总而言之,观察者模式的本质是通过定义一对多的依赖关系,实现了对象之间的消息传递和通知机制,从而实现了对象状态的变化通知和自动更新。

2.何时选用观察者模式

建议在以下情况中选用观察者模式。

  • 当一个对象的状态改变需要通知其他对象,并且不希望对象之间紧密耦合时,可以选用观察者模式。该模式能够解耦主题对象和观察者对象之间的关系,使得它们可以独立演化。

  • 当一个对象的状态改变需要触发一系列相关操作或更新时,可以选用观察者模式。通过将这些操作或更新封装在观察者对象中,可以实现逻辑的解耦和聚集,便于管理和维护。

  • 当一个对象的状态改变需要通知一组相关对象时,可以选用观察者模式。观察者模式允许多个观察者对象订阅主题对象,以便在状态变化时接收通知并采取相应行动。

  • 当需要在运行时动态地添加、删除和管理观察者对象时,可以选用观察者模式。通过观察者模式,可以灵活地添加和移除观察者对象,而不影响主题对象的实现。

3.优缺点

观察者模式具有以下优点

  • 解耦对象间的依赖关系:观察者模式能够将观察者(订阅者)和被观察者(发布者)对象解耦,使得它们之间的依赖关系松散化。被观察者只需要知道观察者的接口,而不需要知道具体的观察者是谁。
  • 支持广播通信:被观察者对象可以同时通知多个观察者对象,使得信息能够广播给多个对象。这样可以方便地实现事件驱动的系统。
  • 符合开闭原则:在观察者模式中,新增观察者或者删除观察者都比较容易,而不需要修改被观察者的代码。这样可以增加系统的灵活性和可扩展性。

观察者模式的缺点是:

  • 可能引入循环引用:在观察者模式中,如果观察者和被观察者之间发生循环引用,可能会导致内存泄漏问题。需要特别注意避免循环引用的发生。

4.观察者模式的结构

在这里插入图片描述

  • Subject:目标对象,通常具有如下功能。
    一个目标可以被多个观察者观察。
    目标提供对观察者注册和退订的维护。
    当目标的状态发生变化时,目标负责通知所有注册的、有效的观察者。
  • Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,以获取目标对象的数据。
  • ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的、有效的观察者,让观察者执行相应的处理。
  • ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致。

5.实现

模拟:手机降价通知用户

在这里插入图片描述

模拟手机降价通知用户

1.目标类

/**
 * @description:通用目标类,也可为抽象类
 */
public class Subject {

    /**
     * 维护一个观察者列表
     */
    private List<Observer> list=new ArrayList<>();

    /**
     * 注册观察者
     * @param observer
     */
    public void attach(Observer observer){
        list.add(observer);
    }

    /**
     * 移除观察者
     * @param observer
     */
    public void detach(Observer observer){
        list.remove(observer);
    }

    /**
     * 通知观察者
     */
    public void notifyObservers(){
        list.stream().forEach(obj->obj.update(this));
    }
}

/**
 * @description:手机目标对象
 */
@Getter
@ToString
public class PhoneSubject extends Subject{

    /**
     * 手机价格
     */
    private double price;

    public void setPrice(double price) {
        this.price = price;
        //价格小于150为降价
        if (price<150){
            //通知用户降价
            notifyObservers();
        }
    }
}

2.观察者类

/**
 * @description:观察者接口
 */
public interface Observer {

    /**
     * 被通知的方法
     * @param subject 目标对象
     */
    void update(Subject subject);
}

/**
 * @description:用户(观察者)
 */
@Data
public class UserObserver implements Observer{

    /**
     * 用户名
     */
    private String name;

    @Override
    public void update(Subject subject) {
        System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject)subject).getPrice());
    }
}

3.测试类

/**
 * @description:测试类
 */
public class Client {

    public static void main(String[] args) {
        UserObserver userObserver1 = new UserObserver();
        userObserver1.setName("张三");

        UserObserver userObserver2 = new UserObserver();
        userObserver2.setName("李四");

        PhoneSubject subject=new PhoneSubject();
        //初始价格为150
        subject.setPrice(150);
        //绑定观察者
        subject.attach(userObserver1);
        subject.attach(userObserver2);

        //涨价不通知
        subject.setPrice(250);

        //降价才通知用户,第一次降价
        subject.setPrice(100);

        //降价才通知用户,第二次降价
        subject.setPrice(88);
    }
}

4.效果
在这里插入图片描述

JDK观察者模式

1.目标类继承Observable接口即可

/**
 * @description:手机目标对象
 */
@Getter
@ToString
public class PhoneSubject2 extends Observable {

    /**
     * 手机价格
     */
    private double price;

    public void setPrice(double price) {
        this.price = price;
        //价格小于150为降价
        if (price<150){
            //通知用户降价
            notifyObservers();
        }
    }
}

里面维护了观察者对象,以及注册、移除观察者,通知观察者等
在这里插入图片描述
2.观察者类实现Observer接口即可

/**
 * @description:用户1
 */
@Data
public class UserObserver2 implements Observer {

    /**
     * 用户名
     */
    private String name;
    
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof PhoneSubject2){
            System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject2)o).getPrice());
        }
    }
}

和手写的一模一样
在这里插入图片描述

3.测试类

/**
 * @author conggq
 * @description:测试类
 * @createTime 2022/11/29 17:27
 */
public class Client {

    public static void main(String[] args) {
        UserObserver userObserver1 = new UserObserver();
        userObserver1.setName("张三");

        UserObserver userObserver2 = new UserObserver();
        userObserver2.setName("李四");

        PhoneSubject2 subject2=new PhoneSubject2();
        //初始价格为150
        subject2.setPrice(150);

        //涨价不通知
        subject.setPrice(250);

        //降价才通知用户,第一次降价
        subject.setPrice(100);

        //降价才通知用户,第二次降价
        subject.setPrice(88);
    }
}

结果一样

模拟摇号通知客户

在这里插入图片描述

1.监听器类

/**
 * @description:事件监听接口
 */
public interface EventListener {

    void doEvent(LotteryResult result);
}

/**
 * @description:MQ监听器
 */
@Slf4j
public class MQEventListener implements EventListener{
    @Override
    public void doEvent(LotteryResult result) {
        log.info("记录用户 {}摇号结果(MQ): {}", result.getUId(),
                result.getMsg());
    }
}

/**
 * @description:短信监听器
 */
@Slf4j
public class MessageEventListener implements EventListener{
    @Override
    public void doEvent(LotteryResult result) {
        log.info("给用户 {} 发送短信通知(短信) : {}", result.getUId(),
                result.getMsg());
    }
}

2.事件管理器

public class EventManager {

    Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

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

    public enum EventType {
        MQ, Message
    }

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

    /**
     * 退订监听器
     */
    public void unsubscribe(Enum<EventType> eventType, EventListener
            listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    /**
     * 通知监听器
     */
    public void notify(Enum<EventType> eventType, LotteryResult result) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.doEvent(result);
        }
    }
}

3.业务类

public abstract class LotteryService {

    private EventManager eventManager;

    public LotteryService() {
        eventManager = new EventManager(EventManager.EventType.MQ,
                EventManager.EventType.Message);
        eventManager.subscribe(EventManager.EventType.MQ, new
                MQEventListener());
        eventManager.subscribe(EventManager.EventType.Message, new
                MessageEventListener());
    }


    public LotteryResult draw(String uId) {
        LotteryResult result = doDraw(uId);

        //根据需要通知那个监听器
        eventManager.notify(EventManager.EventType.MQ, result);
        eventManager.notify(EventManager.EventType.Message, result);
        return result;
    }

    protected abstract LotteryResult doDraw(String uId);

}

public class LotteryServiceImpl extends LotteryService {
    @Override
    protected LotteryResult doDraw(String uId) {

        String lottery = lottery(uId);
        return new LotteryResult(uId, lottery, new Date());
    }

    public String lottery(String uId) {
        return Math.abs(uId.hashCode()) % 2 == 0
                ? "恭喜你,编号 ".concat(uId).concat(" 在本次摇号中签")
                : "很遗憾,编号 ".concat(uId).concat(" 在本次摇号未中签或摇号资格已过期 ");
    }
}

4.返回封装类

@Data
@AllArgsConstructor
public class LotteryResult {

    private String uId;
    private String msg;
    private Date dateTime;
}

5.测试类

@Slf4j
public class LotteryTest {

    public static void main(String[] args) {
        LotteryService lotteryService = new LotteryServiceImpl();
        LotteryResult result = lotteryService.draw("2765789109876");
        log.info("测试结果 {}", JSON.toJSONString(result));
    }
}

5.结果

MQEventListener - 记录用户 2765789109876摇号结果(MQ): 很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期 
MessageEventListener - 给用户 2765789109876 发送短信通知(短信) : 很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期 
LotteryTest - 测试结果 {"dateTime":1685430399322,"msg":"很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期 ","uId":"2765789109876"}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值