设计模式-观察者模式 Observer

观察者模式

  • 一、概述
  • 二、使用场景
  • 三、发布订阅
    • 1) 观察者模式
    • 2) 发布-订阅模式
  • 四、源码使用
    • 1) jdk中的观察者
    • 2) Guava中的消息总线
  • 五、进阶
    • 1) 异步非阻塞模型

一、概述

观察者模式是一种行为设计模式,允许对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。在这种模式中,发生状态改变的对象被称为“主题”(Subject),依赖它的对象被称为“观察者”(Observer)。

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer等等。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

让通过一个简单的例子来实现观察者模式。假设有一个气象站(WeatherStation),需要向许多不同的显示设备(如手机App、网站、电子屏幕等)提供实时天气数据。

首先,需要创建一个Subject接口,表示主题

public interface Subject {/*** 注册观察者* @param observer*/void registerObserver(Observer observer);/*** 删除具体的观察者* @param observer*/void removeObserver(Observer observer);/*** 一旦发生了观察的行为,就通知所有的观察者*/void notifyObservers();
}

接下来,创建一个Observer接口,表示观察者

public interface Observer {/*** 观察的行为发生了,该方法应该被调用* @param newTemperature 更新的温度*/void update(double newTemperature);
}

现在,创建一个具体的主题,如WeatherStation,实现Subject接口:

public class WeatherStation implements Subject{// 温度private double temperature;// 持有多个观察者private final List<Observer> observerList = new ArrayList<>();public void changeTemperature(double newTemperature) {this.temperature = newTemperature;notifyObservers(newTemperature);}@Overridepublic void registerObserver(Observer observer) {observerList.add(observer);}@Overridepublic void removeObserver(Observer observer) {observerList.remove(observer);}@Overridepublic void notifyObservers(double newTemperature) {for (Observer observer : observerList) {observer.update(newTemperature);}}
}

最后,创建一个具体的观察者,如 AppClient/WebClient,实现Observer接口:

public class AppClient implements Observer{@Overridepublic void update(double newTemperature) {System.out.println("App获取最新温度:" + newTemperature);}
}
public class WebClient implements Observer{@Overridepublic void update(double newTemperature) {System.out.println("Web获取最新温度:" + newTemperature);}
}

现在可以创建一个WeatherStation实例并向其注册AppClient观察者。当WeatherStation的数据发生变化时,AppClient会收到通知并更新自己的显示。

public class Main {public static void main(String[] args) {// 定义气象站Subject weatherStation = new WeatherStation();// 定义观察者Observer appClient = new AppClient();Observer webClient = new WebClient();// 建立监听关系weatherStation.registerObserver(appClient);weatherStation.registerObserver(webClient);// 气象站更新温度weatherStation.notifyObservers(25.4);}
}

在这个例子中,创建了一个WeatherStation实例,并向其注册了AppClient、WebClient观察者。当WeatherStation的数据发生变化时,所有观察者都会收到通知并更新自己的显示。 这个例子展示了观察者模式的优点:

  1. 观察者和主题之间的解耦:主题只需要知道观察者实现了Observer接口,而无需了解具体的实现细节。
  2. 可以动态添加和删除观察者:通过调用registerObserver和removeObserver方法,可以在运行时添加和删除观察者。
  3. 主题和观察者之间的通信是自动的:当主题的状态发生变化时,观察者会自动得到通知并更新自己的状态。 观察者模式广泛应用于各种场景,例如事件处理系统、数据同步和更新通知等。学习并掌握观察者模式对于成为一个优秀的Java程序员非常有帮助。

上面的小例子算是观察者模式的“模板代码”,可以反映该模式大体的设计思路。在真实的软件开发中,并不需要照搬上面的模板代码。观察者模式的实现方法各式各样,函数、类的命名等会根据业务场景的不同有很大的差别,比如 register 函数还可以叫作 attach,remove 函数还可以叫作 detach 等等。不过,万变不离其宗,设计思路都是差不多的。

二、使用场景

以下是一些使用观察者设计模式的例子:

  1. 股票行情应用:股票行情应用中,当股票价格发生变化时,需要通知订阅了该股票的投资者。这里,股票价格更新可以作为被观察者,投资者可以作为观察者。当股票价格发生变化时,所有订阅了该股票的投资者都会收到通知并更新自己的投资策略。
  2. 网络聊天室:在网络聊天室中,当有新消息时,需要通知所有在线的用户。聊天室服务器可以作为被观察者,用户可以作为观察者。当有新消息时,聊天室服务器会通知所有在线用户更新聊天记录。
  3. 拍卖系统:在拍卖系统中,当出价发生变化时,需要通知所有关注该拍品的用户。这里,拍卖系统可以作为被观察者,用户可以作为观察者。当出价发生变化时,所有关注该拍品的用户都会收到通知并更新自己的出价策略。
  4. 订阅系统:在订阅系统中,当有新的内容发布时,需要通知所有订阅了该内容的用户。这里,内容发布可以作为被观察者,用户可以作为观察者。当有新内容发布时,所有订阅了该内容的用户都会收到通知并获取最新内容。
  5. 游戏中的事件系统:在游戏中,当某个事件发生时(如角色升级、道具获得等),可能需要通知多个游戏模块进行相应的处理。这里,游戏事件可以作为被观察者,游戏模块可以作为观察者。当游戏事件发生时,所有关注该事件的游戏模块都会收到通知并执行相应的逻辑。
  6. 运动比赛实时更新:在体育比赛中,实时更新比分、技术统计等信息对于球迷和分析师非常重要。在这种场景下,比赛数据更新可以作为被观察者,球迷和分析师可以作为观察者。当比赛数据发生变化时,所有关注比赛的球迷和分析师都会收到通知并更新数据。
  7. 物联网传感器系统:在物联网(IoT)系统中,有很多传感器不断地采集数据,当数据发生变化时,需要通知相关联的设备或系统。在这种场景下,传感器可以作为被观察者,关联的设备或系统可以作为观察者。当传感器数据发生变化时,所有关联的设备或系统都会收到通知并执行相应的操作。
  8. 电子邮件通知系统:在一个任务管理系统中,当任务的状态发生变化(如:新任务分配、任务完成等)时,需要通知相关的人员。这里,任务状态更新可以作为被观察者,相关人员可以作为观察者。当任务状态发生变化时,所有关注该任务的人员都会收到通知并查看任务详情。
  9. 社交网络:在社交网络中,用户关注其他用户以获取实时动态。当被关注的用户发布新动态时,需要通知所有关注者。在这种场景下,被关注的用户可以作为被观察者,关注者可以作为观察者。当被关注的用户发布新动态时,所有关注者都会收到通知并查看动态。

三、发布订阅

发布-订阅模式观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处,但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性,都是用于实现对象间的松耦合通信。可以将发布-订阅模式看作是观察者模式的一种变体或扩展。

我分别解释一下这两种模式。

1) 观察者模式

观察者模式定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。在这个模式中,被观察者和观察者之间存在直接的关联关系。观察者模式主要包括两类对象:被观察者(Subject)和观察者(Observer)

2) 发布-订阅模式

发布-订阅模式(生产者和消费者)与观察者模式类似,但它们之间有一个关键区别:发布-订阅模式引入了一个第三方组件(通常称为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除,从而提高了系统的灵活性和可扩展性。

Java中的发布-订阅模式示例:

public interface Subscriber {void onEvent(Map<String, Object> eventContextMap);
}public class AppSubscriber implements Subscriber{@Overridepublic void onEvent(Map<String, Object> eventContextMap) {System.out.println("app -> 当前的温度是: " + eventContextMap.get("temp"));}
}public class WebSubscriber implements Subscriber{@Overridepublic void onEvent(Map<String, Object> eventContextMap) {System.out.println("web -> 当前的温度是: " + eventContextMap.get("temp"));}
}// 创建消息总线
public class EventBus {// 维护事件(对象,字符串)和订阅者的关系private final Map<String, List<Subscriber>> subscriberMap = new HashMap<>(8);public void registerSubscriber(String eventType, Subscriber subscriber) {// 通过事件类型,来确定有没有已存在订阅者subscriberMap.computeIfAbsent(eventType, v -> new ArrayList<>());// 获取订阅者的集合List<Subscriber> subscriberList = subscriberMap.get(eventType);subscriberList.add(subscriber);// 注册subscriberMap.put(eventType, subscriberList);}public void removeSubscriber(String eventType, Subscriber subscriber) {List<Subscriber> subscriberList = subscriberMap.get(eventType);if (subscriberList != null) {subscriberList.remove(subscriber);}}public void publishEvent(String eventType, Map<String, Object> eventContextMap) {List<Subscriber> subscriberList = subscriberMap.get(eventType);for (Subscriber subscriber : subscriberList) {subscriber.onEvent(eventContextMap);}}
}// 使用示例:
public class WeatherStation {private double temperature;private EventBus eventBus;public WeatherStation(EventBus eventBus) {this.eventBus = eventBus;}public void changeTemperature(double newTemperature) {this.temperature = newTemperature;Map<String, Object> eventContextMap = new HashMap<>(2);eventContextMap.put("temp", newTemperature);eventBus.publishEvent("changeTemperature", eventContextMap);}public static void main(String[] args) {// 创建订阅者AppSubscriber appSubscriber = new AppSubscriber();WebSubscriber webSubscriber = new WebSubscriber();// 构建消息总线EventBus eventBus = new EventBus();eventBus.registerSubscriber("changeTemperature", appSubscriber);eventBus.registerSubscriber("changeTemperature", webSubscriber);// 创建气象站WeatherStation weatherStation = new WeatherStation(eventBus);weatherStation.changeTemperature(25.6);}
}

总结一下两者的区别:

  1. 通信方式:观察者模式中,观察者与被观察者之间存在直接的关联关系,而发布-订阅模式中,发布者和订阅者通过一个第三方组件(消息代理或事件总线)进行通信,彼此之间不存在直接关联关系。
  2. 系统复杂性:发布-订阅模式引入了一个额外的组件(消息代理或事件总线),增加了系统的复杂性,但同时也提高了系统的灵活性和可扩展性。
  3. 使用场景:观察者模式适用于需要将状态变化通知给其他对象的情况,而发布-订阅模式适用于事件驱动的系统,尤其是那些需要跨越多个模块或组件进行通信的场景。 希望这个解释能帮助您理解发布-订阅模式和观察者模式之间的区别。如果您有其他问题,欢迎继续提问。

发布-订阅模式和传统的观察者模式相比,在某些方面具有优势。以下是发布-订阅模式相对于观察者模式的一些优点:

  1. 解耦:在发布-订阅模式中,发布者和订阅者之间没有直接关联,它们通过一个中间组件(消息代理或事件总线)进行通信。这种间接通信可以使发布者和订阅者在运行时动态地添加或删除,从而进一步降低了它们之间的耦合度。
  2. 可扩展性:发布-订阅模式允许您更容易地向系统中添加新的发布者和订阅者,而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展性。
  3. 模块化:由于发布者和订阅者之间的通信通过中间组件进行,您可以将系统划分为更小、更独立的模块。这有助于提高代码的可维护性和可读性。
  4. 异步通信:发布-订阅模式通常支持异步消息传递,这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。
  5. 消息过滤:在发布-订阅模式中,可以利用中间组件对消息进行过滤,使得订阅者只接收到感兴趣的消息。这可以提高系统的性能,减少不必要的通信开销。

然而,发布-订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情况下,观察者模式可能更适合,而在其他情况下,发布-订阅模式可能是更好的选择。

四、源码使用

1) jdk中的观察者

java.util.Observable类实现了主题(Subject)的功能,而java.util.Observer接口则定义了观察者(Observer)的方法。

通过调用Observable对象的notifyObservers()方法,可以通知所有注册的Observer对象,让它们更新自己的状态。

一下是一个使用案例:假设有一个银行账户类,它的余额是可变的。当余额发生变化时,需要通知所有的观察者(比如说银行客户),以便它们更新自己的显示信息。

// 银行账户类
public class BankAccount extends Observable {private double balance;// 构造函数public BankAccount(double balance) {this.balance = balance;}// 存款操作public void deposit(double amount) {balance += amount;setChanged(); // 表示状态已经改变notifyObservers(); // 通知所有观察者}// 取款操作public void withdraw(double amount) {balance -= amount;setChanged(); // 表示状态已经改变notifyObservers(); // 通知所有观察者}// 获取当前余额public double getBalance() {return balance;}// 主函数public static void main(String[] args) {BankAccount account = new BankAccount(1000.0);// 创建观察者Observer observer1 = new Observer() {@Overridepublic void update(Observable o, Object arg) {System.out.println("客户1: 余额已更新为 " + ((BankAccount)o).getBalance());}};Observer observer2 = new Observer() {@Overridepublic void update(Observable o, Object arg) {System.out.println("客户2: 余额已更新为 " + ((BankAccount)o).getBalance());}};// 注册观察者account.addObserver(observer1);account.addObserver(observer2);// 存款操作,触发观察者更新account.deposit(100.0);// 取款操作,触发观察者更新account.withdraw(50.0);}
}

这个案例中,BankAccount类继承了java.util.Observable类,表示它是一个主题(Subject)。在存款或取款操作时,它会调用setChanged()方法表示状态已经改变,并调用notifyObservers()方法通知所有观察者(Observer)。

在主函数中,创建了两个观察者(observer1和observer2),它们分别实现了Observer接口的update()方法。当观察者收到更新通知时,它们会执行自己的业务逻辑,比如更新显示信息。

这个案例演示了观察者模式在银行系统中的应用,通过观察者模式可以实现银行客户对自己账户余额的实时监控。

2) Guava中的消息总线

Guava 库中的 EventBus 类提供了一个简单的消息总线实现,可以帮助在 Java 应用程序中实现发布-订阅模式。以下是一个简单的示例,演示了如何使用 Guava 的 EventBus 来实现一个简单的消息发布和订阅功能。

首先,确保您已将 Guava 添加到项目的依赖项中。如果您使用 Maven,请在 pom.xml 文件中添加以下依赖项:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version>
</dependency>

接下来,定义一个事件类,例如 MessageEvent

public class MessageEvent {private String message;public MessageEvent(String message) {this.message = message;}public String getMessage() {return message;}
}

现在,创建一个订阅者类,例如 MessageSubscriber。在订阅者类中,定义一个方法并使用 @Subscribe 注解标记该方法,以便 EventBus 能够识别该方法作为事件处理器:

public class MessageSubscriber {@Subscribepublic void handleMessageEvent(MessageEvent event) {System.out.println("收到消息: " + event.getMessage());}
}

最后,来看一个使用示例:

public class Main {public static void main(String[] args) {// 创建 EventBus 实例EventBus eventBus = new EventBus();// 创建并注册订阅者MessageSubscriber subscriber = new MessageSubscriber();eventBus.register(subscriber);// 发布事件eventBus.post(new MessageEvent("Hello, EventBus!"));// 取消注册订阅者eventBus.unregister(subscriber);// 再次发布事件(此时订阅者已取消注册,将不会收到消息)eventBus.post(new MessageEvent("Another message"));}
}

在这个示例中,我们创建了一个 EventBus 实例,然后创建并注册了一个 MessageSubscriber 类型的订阅者。当我们使用 eventBus.post() 方法发布一个 MessageEvent 事件时,订阅者的 handleMessageEvent 方法将被调用,并输出收到的消息。

注意,如果订阅者处理事件的方法抛出异常,EventBus 默认情况下不会对异常进行处理。如果需要处理异常,可以在创建 EventBus 实例时传入一个自定义的 SubscriberExceptionHandler

五、进阶

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子。

不同的应用场景和需求下,这个模式也有截然不同的实现方式,之前所列举的所有的例子都是同步阻塞的实现方式,当然我们的观察者设计模式也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。

之前讲到的实现方式,是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。对照上面讲到的用户注册的例子,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。

如果注册接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间

1) 异步非阻塞模型

首先,我们需要创建一个通用的观察者接口Observer和一个被观察者接口Observable

Observer.java:

public interface Observer {void update(String message);
}

Observable.java:

public interface Observable {void addObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}

接下来,我们需要实现一个具体的被观察者类Subject和一个具体的观察者类ConcreteObserver

Subject.java:

public class Subject implements Observable {private List<Observer> observers;private ExecutorService executorService;public Subject() {observers = new ArrayList<>();executorService = Executors.newCachedThreadPool();}@Overridepublic void addObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (Observer observer : observers) {executorService.submit(() -> observer.update(message));}}public void setMessage(String message) {notifyObservers(message);}
}

ConcreteObserver.java:

public class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " received message: " + message);}
}

最后,我们可以创建一个简单的示例来测试实现的异步非阻塞观察者模式。

Main.java:

public class Main {public static void main(String[] args) {Subject subject = new Subject();ConcreteObserver observer1 = new ConcreteObserver("Observer 1");ConcreteObserver observer2 = new ConcreteObserver("Observer 2");ConcreteObserver observer3 = new ConcreteObserver("Observer 3");subject.addObserver(observer1);subject.addObserver(observer2);subject.addObserver(observer3);subject.setMessage("Hello, observers!");// 等待异步任务完成try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
}

在这个示例中,我们使用了ExecutorService的线程池来实现异步非阻塞的通知。每个观察者更新操作都将作为一个任务提交给线程池并异步执行。这将确保性能敏感的场景不会因为观察者的通知而阻塞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/679764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉搜索树题目:二叉搜索树的最小绝对差

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉搜索树的最小绝对差 出处&#xff1a;530. 二叉搜索树的最小绝对差 难度 3 级 题目描述 要求 给定一个二叉…

C#入门及进阶|数组和集合(四):数组的参数传递

数组的参数传递 向方法传递参数有两个方法&#xff0c;一个是“传值”&#xff0c;另一个是“传引用”。 传值时&#xff0c;如果对被调用的方法的值的副本进行修改&#xff0c;不会影响原始变量的值。 1.值传递 通过“传值”方式来传递引用&#xff0c;相当于允许方法直接访问…

什么是Java中的微服务架构,你能列举一些微服务架构的优缺点吗?

什么是Java中的微服务架构&#xff0c;你能列举一些微服务架构的优缺点吗&#xff1f; 微服务架构是一种将单个应用程序拆分为多个小型服务的架构风格&#xff0c;每个服务都运行在自己的进程中&#xff0c;并通过轻量级的通信机制&#xff08;通常是HTTP或者消息队列&#xf…

【DSP】数字信号处理发展里程碑(AI【文心一言】 辅助生成)

在远离尘嚣的学术殿堂中&#xff0c;数字信号处理&#xff08;DSP&#xff09;这一学科犹如一颗璀璨的明珠&#xff0c;其发展历程充满了传奇色彩。下面&#xff0c;就让我们一起穿越时空&#xff0c;回到那些激动人心的时刻&#xff0c;见证数字信号处理从无到有、从弱到强的壮…

java对象内部都有哪些东西

普通对象 对象头 markword 占8字节ClassPointer 指针 :-XX userCompressedClassPointrs 为4字节&#xff0c;不开启为 8字节实例数据 引用类型: -XX userCommpressedOops 为4字节&#xff0c;不开启8字节Padding对齐&#xff0c; 8的倍数 数组对象 对象头&#xff1a;markwor…

算法沉淀——位运算(leetcode真题剖析)

算法沉淀——位运算 常用位运算总结1.基础位运算2.确定一个数中第x位是0还是13.将一个数的第x位改成14.将一个数的第x位改成05.位图6.提取一个数最右边的17.删掉一个数最右边的18.异或运算9.基础例题 力扣题目讲解01.面试题 01.01. 判定字符是否唯一02.丢失的数字03.两整数之和…

【北邮鲁鹏老师计算机视觉课程笔记】05 Hough 霍夫变换

【北邮鲁鹏老师计算机视觉课程笔记】05 Hough 霍夫变换 1 投票策略 考虑到外点率太高 ①让直线上的每一点投票 ②希望噪声点不要给具体的任何模型投票&#xff0c;即噪声点不会有一致性的答案 ③即使被遮挡了&#xff0c;也能把直线找出来 参数空间离散化 直线相当于就是m,b两…

Python 3 中使用 pandas 和 Jupyter Notebook 进行数据分析和可视化

简介 Python 的 pandas 包用于数据操作和分析&#xff0c;旨在让您以直观的方式处理带标签或关联数据。 pandas 包提供了电子表格功能&#xff0c;但由于您正在使用 Python&#xff0c;因此它比传统的图形电子表格程序要快得多且更高效。 在本教程中&#xff0c;我们将介绍如…

git revert回退某次提交

请直接看原文: 【git revert】使用以及理解&#xff08;详解&#xff09;_git revert用法-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- 前言 试验得知:用Reset HEAD方…

mmap函数的详细讲解

mmap&#xff08;&#xff09;函数是一个用于在用户空间和内核空间之间进行文件映射的系统调用。它允许文件在物理内存中的特定区域被映射到进程的地址空间中&#xff0c;从而允许进程通过内存访问操作来读取和写入文件。 函数原型&#xff1a; #include <sys/mman.h> …

笔记---dp---最长上升子序列模型

模型原始题目&#xff1a;AcWing.895.最长上升子序列 题目关系如下&#xff1a; 转化一 AcWing.1017.怪盗基德的滑翔翼 怪盗基德是一个充满传奇色彩的怪盗&#xff0c;专门以珠宝为目标的超级盗窃犯。 而他最为突出的地方&#xff0c;就是他每次都能逃脱中村警部的重重围堵…

ZigBee学习——在官方例程实现组网

✨Z-Stack版本&#xff1a;3.0.2 ✨IAR版本&#xff1a;10.10.1 ✨这篇博客是在善学坊BDB组网实验的基础上进行完善&#xff0c;并指出实现的过程中会出现的各种各样的问题&#xff01; 善学坊教程地址&#xff1a; ZigBee3.0 BDB组网实验 文章目录 一、基础工程选择二、可能遇…

Linux(Ubuntu) 环境搭建:MySQL

注&#xff1a;服务器默认以root用户登录 服务器的终端中输入以下指令&#xff1a; # 安装 MySQL apt install mysql-server # 查看版本 mysql -V # 查看 MySQL 服务状态 systemctl status mysql # 安装完成后&#xff0c;MySQL 服务将自动启动 # MySQL 服务在系统启动时自动…

spring 入门 一

文章目录 Spring简介Spring的优势Spring的体系结构 Spring快速入门Spring程序开发步骤导入Spring开发的基本包坐标编写Dao接口和实现创建Spring核心配置文件在Spring配置文件中配置UserDaoImpl使用Spring的API获得Bean实例 Spring配置文件Bean标签基本配置Bean标签范围配置Bean…

2.10

头文件&#xff1a; #include <sqlite3.h> 编译时候要加上-lsqlite3 gcc a.c -lsqlite3 1&#xff09;sqlite3_open 打开一个数据库&#xff0c;如果数据库不存在&#xff0c;则创建一个数据库 2&#xff09;sqlite3_close 关闭数据库&#xff0c;断开句柄所拥有的资…

HarmonyOS 横屏调试与真机横屏运行

我们有些程序 需要横屏才能执行出效果 我们在预览器上 点击如下图指向出 就进入一个横屏调试了 但 我们真机运行 依旧是竖着的 我们如下图 找到 module.json5 在 abilities 下面 第一个对象 最下面 加上 "orientation": "landscape"然后 我们再真机运…

Oracle表结构转成MySQL表结构

在将Oracle数据库表结构转换为MySQL数据库表结构时&#xff0c;需要考虑两大数据库系统之间的差异。以下是一些基本步骤和注意事项&#xff0c;帮助您进行转换&#xff1a;1、字符集和排序规则&#xff1a; Oracle使用的是固定的字符集和排序规则&#xff0c;而MySQL使用的是可…

Rust变量与常量介绍

Rust是一门注重安全性和性能的系统编程语言&#xff0c;其中变量和常量的概念有着独特的设计和特性。在本文中&#xff0c;我们将深入了解Rust中的变量和常量&#xff0c;并解释它们之间的区别&#xff0c;同时通过多个例子进行说明。 Rust常量 在Rust中&#xff0c;常量是不…

第三百二十一回

文章目录 1. 概念介绍2. 使用方法2.1 基本用法2.2 缓冲原理 3. 示例代码4. 内容总结 我们在上一章回中介绍了"FadeInImage组件"相关的内容&#xff0c;本章回中将介绍CachedNetworkImage组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

搜索专项---最短路模型

文章目录 迷宫问题武士风度的牛抓住那头牛 一、迷宫问题OJ链接 本题思路:只需要记录各个点是有哪个点走过来的&#xff0c;就能递推得出路径。记录前驱假设从 1,1 这个点向下走到了2, 1&#xff0c;则将2,1这个点的前驱记为1,1。这样&#xff0c;将整张地图 bfs 后&#xff0c…