【HF设计模式】02-观察者模式

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

《Head First设计模式》第2章笔记:结合示例应用和代码,介绍观察者模式,包括遇到的问题、应用的 OO 原则、达到的效果。

目录

  • 摘要
  • 1 示例应用
  • 2 遇到问题
  • 3 引入设计模式
    • 3.1 认识观察者模式
    • 3.2 观察者模式定义
    • 3.3 OO 原则:松耦合设计
    • 3.4 其它 OO 原则的应用
    • 3.5 系统类图
  • 4 示例代码
    • 4.1 Java 示例
    • 4.2 C++11 示例
  • 5 设计工具箱
    • 5.1 OO 基础
    • 5.2 OO 原则
    • 5.3 OO 模式
  • 参考


1 示例应用

示例应用是一个“气象观测站”,系统组成如下:

温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
传感器
气象站
湿度
传感器
气压
传感器
Weather
Data
当前天气
天气预报
气象统计
<<待扩展>>
热度指数
...
  1. 物理设备“气象站”:从传感器获取实时的气象数据,包括温度、湿度和气压;
  2. 第3方模块 WeatherData:一边获取“气象站”的气象数据,一边更新每个“显示组件”的气象数据;
  3. 需要开发的“显示组件”:包括当前天气组件、天气预报组件和气象统计组件;
    未来还会扩展其它“显示组件”。

第3方模块 WeatherData 提供的集成接口如下:

WeatherData
getTemperature()
getHumidity()
getPressure()
measurementsChanged()

其中 measurementsChanged() 方法需要我们来实现:

/** 每当气象测量(weather measurements: 温度、湿度、气压)更新时,这个方法就会被调用*/
public void measurementsChanged() {// TODO:需要添加方法实现
}

作为 WeatherData 的集成方,

  • 我们需要在 measurementsChanged() 方法被调用时,使用最新的测量数据更新所有的“显示组件”;
  • 我们不需要关注 measurementsChanged() 方法是“如何”被调用的。

2 遇到问题

在明确需求后,我们有了第1版 measurementsChanged() 的实现:

public void measurementsChanged() {float temp = getTemperature();   // 获取温度值float humidity = getHumidity();  // 获取湿度值float pressure = getPressure();  // 获取气压值currentConditionsDisplay.update(temp, humidity, pressure);  // 更新“当前天气”显示组件statisticsDisplay.update(temp, humidity, pressure);         // 更新“气象统计”显示组件forecastDisplay.update(temp, humidity, pressure);           // 更新“天气预报”显示组件
}

思考题

对于第1版 measurementsChanged() 实现,下列哪些说法是正确的?(多选)【答案在第 20 行】
Based on our first implementation, which of the following apply? (Choose all that apply.)A. 针对具体实现编程,而不是针对接口编程。We are coding to concrete implementations, not interfaces.
B. 每次添加新的显示组件,都得修改这部分代码。For every new display we’ll need to alter this code.
C. 没有办法在运行时添加(或移除)显示组件。We have no way to add (or remove) display elements at runtime.
D. 这些显示组件没有实现一个共同的接口。The display elements don’t implement a common interface.
E. 没有对变化的部分进行封装。We haven’t encapsulated the part that changes.
F. 违反了 WeatherData 类的封装性。We are violating encapsulation of the WeatherData class.答案:A B C E
解析:
A. 参考下文 “其它 OO 原则的应用” 部分
B. 需要添加对新的显示组件的调用 newDisplay.update()
C. 所有显示组件的调用都被硬编码
D. 使用了共同的 update(temp, humidity, pressure) 接口
E. 变化的部分包括:新的显示组件、添加(或移除)显示组件
F. 参考上一篇文章:01-策略模式 => 5.1 OO 基础 => 封装

3 引入设计模式

3.1 认识观察者模式

你知道报纸或杂志是怎么订阅的吗?

序号报社某读者
1开始运营,出版报纸
2想看报纸时:
向报社订阅,成为订阅者
3接受订阅,将“某读者”加入订阅者名单
4每当有新报纸出版时:
向名单中的每一个订阅者递送报纸
5收到报纸
6不想再看报纸时:
向报社取消订阅
7接受取消,将“某读者”移出订阅者名单
不再向其递送报纸

只要报社还在运营,就会一直有人、酒店、航班和其他企业订阅和取消订阅报纸。

出版者 + 订阅者 = 观察者模式

如果你了解报纸的订阅,就会在很大程度上理解观察者模式。只是在观察者模式中,将出版者叫作主题(SUBJECT),将订阅者叫作观察者(OBSERVERS)。

主题某对象序号报社某读者
开始运行,管理某些重要的数据1开始运营,出版报纸
想了解主题数据时:
向主题注册,成为观察者
2想看报纸时:
向报社订阅,成为订阅者
接受注册,将“某对象”加入观察者列表3接受订阅,将“某读者”加入订阅者名单
每当数据发生改变时:
向列表中的每一个观察者发送通知,
同时附带数据,或者需要自取数据
4每当有新报纸出版时:
向名单中的每一个订阅者递送报纸
得到数据5收到报纸
不想再了解主题数据时:
向主题取消注册
6不想再看报纸时:
向报社取消订阅
接受取消,将“某对象”移出观察者列表
不再向其发送通知
7接受取消,将“某读者”移出订阅者名单
不再向其递送报纸

3.2 观察者模式定义

观察者模式(Observer Pattern)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
Define a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

这里的“一对多”,是指一个主题对应多个观察者,当主题的数据发生改变时,所有观察者都会得到通知并被自动更新。

将模式应用于“气象观测站”:一个 WeatherData 对应多个“显示组件”,当 WeatherData 的气象测量值发生改变时,所有“显示组件”都会得到通知并被自动更新。正是我们期望的情况。

下面结合类图,进一步了解观察者模式的设计细节。

«abstract»
Subject
-List<Observer> observers
+registerObserver(Observer)
+removeObserver(Observer)
+notifyObservers() : CallUpdateOfEachObserver
«interface»
Observer
+update()
ConcreteSubject
-subjectState
+getState() : ReturnSubjectState
+setState()
ConcreteObserver
-ConcreteSubject subject
-observerState :ConsistentWithSubjectState
+update() : CallGetStateOfSubject
  1. 主题抽象类 Subject
    • 提供 registerObserver()removeObserver() 方法,用于注册和移除观察者,其中观察者为 Observer 接口类型;
    • 使用 observers 列表保存处于注册状态的观察者;
    • 提供 notifyObservers() 方法,用于向 observers 列表中的每一个观察者发送更新通知;
      在通知过程中,会统一调用 Observer 接口的 update() 方法。
      注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallUpdateOfEachObserver
  2. 观察者接口 Observer
    • 定义 update() 方法,由主题在发送更新通知时调用。
  3. 具体主题 ConcreteSubject,继承 Subject 抽象类:
    • 使用 subjectState 保存内部状态;
    • 提供 setState()getState() 方法,用于访问其内部状态;
    • 可以在 setState() 的内部直接调用 notifyObservers() 向观察者发送通知;
      也可以在更适当的时机(例如,在一系列的状态改变完成之后,即多次 setState() 之后),再发送通知。
  4. 具体观察者 ConcreteObserver,实现 Observer 接口:
    • 使用 observerState 保存内部状态;
    • 实现 update() 方法,获得主题的更新通知;
      在方法内部,可以通过具体主题的 getState() 获取主题状态,并同步更新其内部状态 observerState

延伸阅读:《设计模式:可复用面向对象软件的基础》 5.7 Observer(观察者)— 对象行为型模式 [P219-227]

3.3 OO 原则:松耦合设计

当两个对象之间松耦合时,它们可以交互,但是对彼此所知甚少。
When two objects are loosely coupled, they can interact, but they typically have very little knowledge of each other.

观察者模式实现了主题与观察者之间的松耦合,是松耦合设计的典范:

  1. 对于观察者,主题仅仅知道其实现了特定的接口(Observer 接口);
    • 主题不需要知道观察者的具体类是什么、能做什么,或者其它任何信息。
  2. 主题进行广播通信(notifyObservers()),依赖的是观察者的列表,而不是某(几)个观察者;
    • 可以在任何时候(包括运行时),添加观察者(registerObserver())或移除观察者(removeObserver())。
  3. 添加新类型的观察者时,不需要修改主题;
    • 只需要新的类型实现 Observer 接口,并通过 registerObserver() 注册成为观察者。
  4. 主题和观察者是独立封装,所以
    • 修改主题或观察者其中一方时,不会影响另一方(只要它们遵守彼此的接口约定);
    • 可以独立的复用主题,或独立的复用观察者。

因为对象之间的依赖降到了最低,所以松耦合的设计让我们能够创建“可以应对变化的、有弹性的 OO 系统”。
Loosely coupled designs allow us to build flexible OO systems that can handle change because they minimize the interdependency between objects.

设计原则
尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact.

3.4 其它 OO 原则的应用

3.4.1 封装变化

识别应用中变化的方面,把它们和不变的方面分开。
Identify the aspects of your application that vary and separate them from what stays the same.

  • 书上描述
    • 在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。使用这个模式,可以改变依赖于主题状态的对象,却不必改变主题。这叫作提前规划!
      The thing that varies in the Observer Pattern is the state of the Subject and the number and types of Observers. With this pattern, you can vary the objects that are dependent on the state of the Subject, without having to change that Subject. That’s called planning ahead!
  • 个人理解
    • 变化方面1:观察者的类型。封装方法:所有类型的观察者都实现 Observers 接口,通过统一的接口进行访问。
    • 变化方面2:观察者的数量。封装方法:将观察者放到 List 容器中,通过容器提供的统一的接口进行访问。

3.4.2 针对接口编程

针对接口编程,而不是针对实现编程。
Program to an interface, not an implementation.

主题和观察者都使用接口。主题使用观察者接口向观察者发送通知,而观察者则通过主题接口进行注册,以及获得通知。这使系统松散耦合,易于扩展和维护。
Both the Subject and Observers use interfaces. The Subject keeps track of objects implementing the Observer interface, while the Observers register with, and get notified by, the Subject interface. As we’ve seen, this keeps things nice and loosely coupled.

3.4.3 优先使用组合

优先使用组合而不是继承。
Favor composition over inheritance.

观察者模式使用“组合”来将任意数目的观察者组合进主题。观察者与主题之间的关系,不是通过继承建立的,而是在运行时通过组合的方式建立的。
The Observer Pattern uses composition to compose any number of Observers with their Subject. These relationships aren’t set up by some kind of inheritance hierarchy. No, they are set up at runtime by composition!

3.5 系统类图

使用观察者模式重新设计“气象观测站”,系统类图如下:

«interface»
Subject
registerObserver(Observer)
removeObserver(Observer)
notifyObservers()
WeatherData
List<Observer> observers
registerObserver(Observer)
removeObserver(Observer)
notifyObservers()
getTemperature()
getHumidity()
getPressure()
measurementsChanged()
setMeasurements()
«interface»
Observer
update()
«interface»
DisplayElement
display()
CurrentConditionsDisplay
WeatherData subject
update()
display()
StatisticsDisplay
WeatherData subject
update()
display()
ForecastDisplay
WeatherData subject
update()
display()
OtherDisplay
WeatherData subject
update()
display()
  1. WeatherData 是具体主题
    • 实现 Subject 接口,实现注册、移除和通知观察者的方法;
    • 提供设置和获取状态的方法。
  2. 各种“显示组件”,包括 CurrentConditionsDisplayStatisticsDisplayForecastDisplayOtherDisplay
    • 是具体观察者,实现 Observer 接口,实现 update() 方法,用于更新气象测量数据;
    • 实现 DisplayElement 接口,实现 display() 方法,用于更新显示界面。

新的 measurementsChanged() 实现如下:

public void measurementsChanged() {notifyObservers();  // 主题接口
}public void notifyObservers() {for (Observer observer : observers) {  // 容器接口observer.update();  // 观察者接口}
}

通过采用观察者模式,第1版 measurementsChanged() 实现中的问题 都得到了解决,新的系统 可以应对变化并具有弹性。

4 示例代码

4.1 Java 示例

相关接口定义:

// Observer.java
public interface Observer {// 通过 update 参数传递数据(由主题 push 数据),会有如下不足:(由观察者 pull 数据是另一种选择)// 1. 某些观察者类可能并不需要传递的所有数据// 2. 如果增加新的数据(如风速),需要修改所有观察者类的 update 方法public void update(float temperature, float humidity, float pressure);
}// DisplayElement.java
public interface DisplayElement {public void display();
}// Subject.java
public interface Subject {public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();
}

WeatherData 类定义:

// WeatherData.java
public class WeatherData implements Subject {private List<Observer> observers;private float temperature;private float humidity;private float pressure;public WeatherData() { observers = new ArrayList<Observer>(); }public void registerObserver(Observer o) { observers.add(o); }public void removeObserver(Observer o) { observers.remove(o); }public void notifyObservers() {for (Observer observer : observers) {observer.update(temperature, humidity, pressure);}}public void measurementsChanged() { notifyObservers(); }public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}public float getTemperature() { return temperature; }public float getHumidity() { return humidity; }public float getPressure() { return pressure; }
}

“显示组件”类定义:

// CurrentConditionsDisplay.java
public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private float pressure;private WeatherData weatherData;public CurrentConditionsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;display();}public void display() {System.out.println("Current conditions: \n\t" + temperature+ "F degrees, " + humidity + "% humidity, " + pressure + " pressure");}
}// StatisticsDisplay.java
public class StatisticsDisplay implements Observer, DisplayElement {private float maxTemp = 0.0f;private float minTemp = 200;private float tempSum = 0.0f;private int numReadings;private WeatherData weatherData;public StatisticsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {tempSum += temp;numReadings++;if (temp > maxTemp) { maxTemp = temp; }if (temp < minTemp) { minTemp = temp; }display();}public void display() {System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)+ "/" + maxTemp + "/" + minTemp);}
}

测试代码:

// WeatherStation.java
public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);weatherData.setMeasurements(80, 65, 30.4f);weatherData.setMeasurements(82, 70, 29.2f);weatherData.removeObserver(currentDisplay);weatherData.setMeasurements(78, 90, 29.2f);}
}

Java 曾经提供 Observable(Subject)类和 Observer 接口。但从 Java9 开始,不再赞成使用。

附上代码便于参考:

// Observer.java
public interface Observer {void update(Observable o, Object arg);
}// Observable.java
public class Observable {private boolean changed = false;private Vector<Observer> obs;public Observable() { obs = new Vector<>(); }public synchronized void addObserver(Observer o) {if (o == null)throw new NullPointerException();if (!obs.contains(o)) {obs.addElement(o);}}public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }public synchronized void deleteObservers() { obs.removeAllElements(); }public synchronized int countObservers() { return obs.size(); }public void notifyObservers() { notifyObservers(null); }public void notifyObservers(Object arg) {Object[] arrLocal;synchronized (this) {/* We don't want the Observer doing callbacks into arbitrary code while holding its own Monitor.* The code where we extract each Observer from the Vector and store the state of the Observer* needs synchronization, but notifying observers does not (should not). * The worst result of any potential race-condition here is that:* 1) a newly-added Observer will miss a notification in progress* 2) a recently unregistered Observer will be wrongly notified when it doesn't care*/if (!changed)return;arrLocal = obs.toArray();clearChanged();}for (int i = arrLocal.length - 1; i >= 0; i--)((Observer) arrLocal[i]).update(this, arg);}public synchronized boolean hasChanged() { return changed; }protected synchronized void setChanged() { changed = true; }protected synchronized void clearChanged() { changed = false; }
}

4.2 C++11 示例

相关接口定义:

struct Observer {virtual ~Observer() = default;virtual void update(const class Subject& s) = 0;
};struct DisplayElement {virtual ~DisplayElement() = default;virtual void display() const = 0;
};struct Subject {virtual ~Subject() = default;virtual void registerObserver(std::shared_ptr<Observer> o) {for (const auto& observer : observers) {if (auto sp = observer.lock()) {if (sp == o) {return;}}}observers.push_back(o);}virtual void removeObserver(std::shared_ptr<Observer> o) {std::list<std::weak_ptr<Observer>>::const_iterator it;for (it = observers.begin(); it != observers.end(); ++it) {if (auto sp = it->lock()) {if (sp == o) {observers.erase(it);return;}}}}virtual void notifyObservers() {std::list<std::weak_ptr<Observer>>::const_iterator it;for (it = observers.begin(); it != observers.end();) {if (auto sp = it->lock()) {sp->update(*this);++it;} else {it = observers.erase(it);}}}protected:Subject() = default;std::list<std::weak_ptr<Observer>> observers;
};

WeatherData 类定义:

struct WeatherData : public Subject {void measurementsChanged() { notifyObservers(); }void setMeasurements(float temperature, float humidity, float pressure) {this->temperature = temperature;this->humidity = humidity;this->pressure = pressure;measurementsChanged();}float getTemperature() const { return temperature; }float getHumidity() const { return humidity; }float getPressure() const { return pressure; }private:float temperature;float humidity;float pressure;
};

“显示组件”类定义:

struct CurrentConditionsDisplay : public Observer, public DisplayElement {void update(const Subject& s) override {const WeatherData* weatherData = dynamic_cast<const WeatherData*>(&s);if (weatherData) {temperature = weatherData->getTemperature();humidity = weatherData->getHumidity();pressure = weatherData->getPressure();display();}}void display() const override {std::cout << "Current conditions: \n\t" << temperature << "F degrees, "<< humidity << "% humidity, " << pressure << " pressure\n";}private:float temperature;float humidity;float pressure;
};struct StatisticsDisplay : public Observer, public DisplayElement {void update(const Subject& s) override {const WeatherData* weatherData = dynamic_cast<const WeatherData*>(&s);if (weatherData) {float temp = weatherData->getTemperature();tempSum += temp;numReadings++;if (temp > maxTemp) maxTemp = temp;if (temp < minTemp) minTemp = temp;display();}}void display() const override {std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings) << "/"<< maxTemp << "/" << minTemp << "\n";}private:float maxTemp = 0.0f;float minTemp = 200;float tempSum = 0.0f;int numReadings = 0;
};

测试代码:

#include <iostream>
#include <list>
#include <memory>// 在这里添加相关接口和类的定义int main() {auto weatherData = std::make_shared<WeatherData>();auto currentDisplay = std::make_shared<CurrentConditionsDisplay>();auto statisticsDisplay = std::make_shared<StatisticsDisplay>();weatherData->registerObserver(currentDisplay);weatherData->registerObserver(statisticsDisplay);weatherData->setMeasurements(80, 65, 30.4f);weatherData->setMeasurements(82, 70, 29.2f);weatherData->removeObserver(currentDisplay);weatherData->setMeasurements(78, 90, 29.2f);
}

5 设计工具箱

5.1 OO 基础

OO 基础回顾

  1. 抽象(Abstraction)
  2. 封装(Encapsulation)
  3. 继承(Inheritance)
  4. 多态(Polymorphism)

5.2 OO 原则

5.2.1 新原则

尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact.

5.2.2 原则回顾

  1. 封装变化。
    Encapsulate what varies.
  2. 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  3. 优先使用组合,而不是继承。
    Favor composition over inheritance.

5.3 OO 模式

5.3.1 新模式

观察者模式:

  • 定义对象之间的一对多依赖,
    The Observer Pattern defines a one-to-many dependency between objects
  • 这样一来,当一个对象改变状态时,它的所有依赖者都会被通知并自动更新。
    so that when one object changes state, all of its dependents are notified and updated automatically.

5.3.2 模式回顾

策略模式:

  • 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
    Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.
  • 让算法的变化独立于使用算法的客户。
    Strategy lets the algorithm vary independently from clients that use it.

参考

  1. [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
  2. [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
  3. wickedlysmart: Head First设计模式 Java 源码

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.

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

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

相关文章

AI - RAG中的状态化管理聊天记录

AI - RAG中的状态化管理聊天记录 大家好&#xff0c;今天我们来聊聊LangChain和LLM中一个重要的话题——状态化管理聊天记录。在使用大语言模型(LLM)的时候&#xff0c;聊天记录&#xff08;History&#xff09;和状态&#xff08;State&#xff09;管理是非常关键的。那我们先…

【AI学习】Mamba学习(二十):Mamba是一种线性注意力

论文《Demystify Mamba in Vision: A Linear Attention Perspective 》从线性注意力的视角&#xff0c;对Mamba 进行了阐释。 论文名称&#xff1a;Demystify Mamba in Vision: A Linear Attention Perspective (Arxiv 2024.05) 论文地址&#xff1a;https://arxiv.org/pdf/24…

李宏毅深度学习-Pytorch Tutorial2

什么是张量&#xff1f; 张量&#xff08;Tensor&#xff09;是深度学习和机器学习中一个非常基础且重要的概念。在数学上&#xff0c;张量可以被看作是向量和矩阵的泛化。简单来说&#xff0c;张量是一种多维数组&#xff0c;它可以表示标量&#xff08;0维&#xff09;、向量…

POI遍历行所有单元格的两种方式,getPhysicalNumberOfCells方式有问题,勿用

今天看POI源码的时候&#xff0c;发现HSSFWorkbook类型的工作簿&#xff0c;行数据是用TreeMap<Integer, HSSFRow>存储的&#xff0c;列数据是用HSSFCell[]数组来存的&#xff1b;XSSFWorkbook类型的工作簿&#xff0c;行数据是用SortedMap<Integer, XSSFRow>存储的…

DataSophon集成CMAK KafkaManager

本次集成基于DDP1.2.1 集成CMAK-3.0.0.6 设计的json和tar包我放网盘了. 通过网盘分享的文件&#xff1a;DDP集成CMAK 链接: https://pan.baidu.com/s/1BR70Ajj9FxvjBlsOX4Ivhw?pwdcpmc 提取码: cpmc CMAK github上提供了zip压缩包.将压缩包解压之后 在根目录下加入启动脚本…

ArrayList常见操作源码逐句剖析

目录 前言 正文 1.需要了解的一些字段属性 1.存储 ArrayList 元素的数组缓冲区。 2.集合的大小 3.默认集合容量大小 2.ArrayList对象创建 1.无参构造 2.有参构造1 3.有参构造2 3.添加元素add(E e)以及扩容机制 ​编辑 后言 前言 源码的剖析有助于理解设计模式&…

重磅更新:CnosDB 2.3.5.4 版本上线, 性能提升,问题修复一网打尽

&#x1f4e2; 重磅更新&#xff1a;CnosDB 2.3.5.4 版本上线, 性能提升&#xff0c;问题修复一网打尽 &#x1f4e2; 我们很高兴地向大家介绍最新版本的更新&#xff0c;以下是本次更新的主要内容&#xff1a; &#x1f539; 版本号&#xff1a;2.3.5.4 &#x1f539; 发布…

SD-WAN 2.0 在金融行业的典型应用场景

目录 全扁平化组网 场景需求 应用方案 SD-WAN 2.0 在金融行业的创新实践 SD-WAN5G提高金融行业网络接入可靠性 全扁平化组网 随着金融机构数字化转型的推进&#xff0c;机构业务的多样性、复杂性、 个性化等要求&#xff0c;对现有的金融机构网络架构与网管人员运维模式提出…

如何延长相机电池续航时间

如果你曾在拍摄过程中突然发现相机电池电量不足&#xff0c;就会知道那有多让人紧张和沮丧了。无论你是在拍摄小朋友的生日派对、家庭聚会&#xff0c;还是作为一名专业摄影师在工作&#xff0c;保持电池有电都是至关重要的。否则&#xff0c;你就有可能错过精彩瞬间&#xff0…

C#开发-集合使用和技巧(十)Union用法-并集

在 C# 中&#xff0c;IEnumerable 的 Union 方法用于返回两个序列的并集。Union 方法会去除重复的元素&#xff0c;确保结果集中每个元素都是唯一的。以下是 Union 方法的基本用法&#xff1a; 基本语法 public static IEnumerable<TSource> Union<TSource>(this…

轻量化特征融合 | YOLOv11 引入一种基于增强层间特征相关性的轻量级特征融合网络 | 北理工新作

本改进已同步到Magic框架 摘要—无人机图像中的小目标检测由于分辨率低和背景融合等因素具有挑战性,导致特征信息有限。多尺度特征融合可以通过捕获不同尺度的信息来增强检测,但传统策略效果不佳。简单的连接或加法操作无法充分利用多尺度融合的优势,导致特征之间的相关性不…

ABAP 系统变量SY-INDEX与SY-TABIX的区别

ABAP系统变量SY-INDEX与SY-TABIX都是在循环中使用&#xff1a; SY-INDEX在Do...EndDo和While...EndWhile中起作用&#xff1b; SY-TABIX在Loop...EndLoop中有效。 详见如下实例&#xff1a; REPORT ztest_index_tabix.DATA:lit_vbak TYPE STANDARD TABLE OF vbak,lwa_vbak …

方案拆解 | 打击矩阵新规频出!2025矩阵营销该怎么玩?

社媒平台的矩阵营销又要“变天”了&#xff1f;&#xff01; 11月18日&#xff0c;小红书官方发表了被安全薯 称为“小红书史上最严打击黑灰产专项”新规&#xff0c;其中就包括黑灰产矩阵号的公告。 ▲ 图源&#xff1a;小红书 实际上&#xff0c;不包括这次&#xff0c;今年…

Lua语言入门 - Lua 数组

Lua 数组 数组&#xff0c;就是相同数据类型的元素按一定顺序排列的集合&#xff0c;可以是一维数组和多维数组。 在 Lua 中&#xff0c;数组不是一种特定的数据类型&#xff0c;而是一种用来存储一组值的数据结构。 实际上&#xff0c;Lua 中并没有专门的数组类型&#xff…

SVM的基本思想

一、SVM的基本思想 SVM的基本思想是在样本的向量空间中寻找一个超平面&#xff0c;使得两类样本被分割在平面的两端。这样的平面理论上有无穷多个&#xff0c;但SVM的目标是找到一个最优的超平面&#xff0c;即两侧距离超平面最近的样本点到超平面的距离被最大化的超平面。这个…

Java基于SpringBoot的网上订餐系统,附源码

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

【 工具变量】IPCC碳排放因子数据测算表

一、数据简介&#xff1a; 排放因子法是IPCC提出的一种碳排放估算方法&#xff0c;也是目前适用范围最广、应用最为普遍的方法。将各类能源消耗的实物统计量转变为标准统计量&#xff0c;再乘以各自的碳排放因子&#xff0c;加总之后就可以得到碳排放总量。如果按照ISO14064标…

备忘录模式的理解和实践

引言 在软件开发中&#xff0c;我们经常会遇到需要保存对象状态并在某个时间点恢复到该状态的需求。这种需求类似于我们平时说的“后悔药”&#xff0c;即允许用户撤销之前的操作&#xff0c;恢复到某个之前的状态。备忘录模式&#xff08;Memento Pattern&#xff09;正是为了…

day35—蓝桥杯2024年第16届校赛模拟第二期-T4(最小花费)

【问题描述】 小蓝有一个整数&#xff0c;初始值为 1 &#xff0c;他可以花费一些代价对这个整数进行变换。 小蓝可以花费 1 的代价将整数增加 1 。 小蓝可以花费 3 的代价将整数增加一个值&#xff0c;这个值是整数的数位中最大的那个&#xff08;1 到 9&#xff09;。 小蓝可…

Vue.js面试题

0. Vue.js 做了些什么&#xff1f; Vue.js 作为现代前端框架&#xff0c;提供了一系列功能和工具&#xff0c;帮助开发者更高效地开发 Web 应用。它不仅是一个简单的 UI 渲染库&#xff0c;还涵盖了很多与开发效率、应用结构、性能优化相关的内容。Vue 的核心特性包括&#xf…