观察者(订阅)模式

编程入门 行业动态 更新时间:2024-10-28 20:27:25

<a href=https://www.elefans.com/category/jswz/34/1765125.html style=观察者(订阅)模式"/>

观察者(订阅)模式

文章目录

    • 思考观察者模式
      • 1.观察者模式的本质
      • 2.何时选用观察者模式
      • 3.优缺点
      • 4.观察者模式的结构
      • 5.实现
        • 模拟手机降价通知用户
        • JDK观察者模式
        • 模拟摇号通知客户

思考观察者模式

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

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;@Overridepublic 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();//初始价格为150subject.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;@Overridepublic 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();//初始价格为150subject2.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{@Overridepublic void doEvent(LotteryResult result) {log.info("记录用户 {}摇号结果(MQ): {}", result.getUId(),result.getMsg());}
}/*** @description:短信监听器*/
@Slf4j
public class MessageEventListener implements EventListener{@Overridepublic 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, EventListenerlistener) {List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 退订监听器*/public void unsubscribe(Enum<EventType> eventType, EventListenerlistener) {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, newMQEventListener());eventManager.subscribe(EventManager.EventType.Message, newMessageEventListener());}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 {@Overrideprotected 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"}

更多推荐

观察者(订阅)模式

本文发布于:2024-02-12 20:27:50,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1689290.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:观察者   模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!