设计模式的六大基本原则

写代码设计原则是指在编写代码时,遵循一些通用的指导原则,以确保代码的可读性、可维护性、可扩展性和可重用性。这些原则有助于开发人员创建出高质量的软件系统。下面我将介绍几个常见的代码设计原则,并通过C++代码例子来说明它们的应用。

1. 单一原则 (Single Responsibility Principle, SRP)

含义:一个类、函数或模块只应负责一项职责,即只有一个引起它变化的原因。

        换句话说,一个类应该只有一个职责。如果一个类有多个职责,那么这些职责就可能会相互干扰,导致类的设计变得脆弱和难以维护。

例子1:假设我们要编写一个程序来管理学生的信息,包括成绩和个人信息。如果不遵循单一职责原则,我们可能会创建一个包含所有功能的庞大类。但是,按照单一职责原则,我们应该将不同的职责分离到不同的类中。

// 违反单一职责原则  
class StudentManager {  
public:  void AddStudent(Student s) { /*...*/ }  void UpdateGrade(Student s, Grade g) { /*...*/ }  void PrintStudentInfo(Student s) { /*...*/ }  // ... 其他与学生管理相关的功能  
};  // 遵循单一职责原则  
class StudentRepository {  
public:  void AddStudent(Student s) { /*...*/ }  // ... 其他与学生存储相关的功能  
};  class GradeManager {  
public:  void UpdateGrade(Student s, Grade g) { /*...*/ }  // ... 其他与成绩管理相关的功能  
};  class StudentPrinter {  
public:  void PrintStudentInfo(Student s) { /*...*/ }  // ... 其他与学生信息打印相关的功能  
};

在这个例子中,我们将学生管理功能拆分为三个类:StudentRepository 负责学生信息的存储,GradeManager 负责成绩管理,StudentPrinter 负责打印学生信息。这样每个类都只负责一个特定的职责。

例子2:假设我们有一个类既负责处理用户认证又负责处理用户授权。

违反单一职责原则的代码可能是这样的:

class AuthManager {  
public:  void AuthenticateUser(const std::string& username, const std::string& password) {  // 实现用户认证逻辑  }  void AuthorizeUser(const std::string& username, const std::string& resource) {  // 实现用户授权逻辑  }  
};

遵循单一职责原则的代码应该是这样的:

class AuthenticationManager {  
public:  void AuthenticateUser(const std::string& username, const std::string& password) {  // 实现用户认证逻辑  }  
};  class AuthorizationManager {  
public:  void AuthorizeUser(const std::string& username, const std::string& resource) {  // 实现用户授权逻辑  }  
};

在这个改进的例子中,我们将AuthManager类的职责拆分成了两个独立的类:AuthenticationManagerAuthorizationManager。每个类都只有一个明确的职责,这使得代码更加清晰、可维护,并且降低了类之间职责的耦合性。

2. 开闭原则(Open/Closed Principle, OCP)

含义:软件实体(类、模块、函数等)应该是可扩展的,但不可修改的。也就是说,当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改现有代码。

例子:假设我们有一个简单的日志系统,它可以记录不同级别的日志信息。最初,系统只支持记录“信息”和“警告”级别的日志。后来,我们需要添加一个新的日志级别“错误”,而不修改已有的代码。

违背开闭原则的C++例子1

#include <iostream>  
#include <string>  // 日志级别枚举  
enum class LogLevel {  Info,  Warning  
};  // 日志记录器基类  
class Logger {  
public:  virtual ~Logger() = default;  virtual void log(LogLevel level, const std::string& message) const = 0;  
};  // 控制台日志记录器实现  
class ConsoleLogger : public Logger {  
public:  void log(LogLevel level, const std::string& message) const override {  switch (level) {  case LogLevel::Info:  std::cout << "[Info] " << message << std::endl;  break;  case LogLevel::Warning:  std::cout << "[Warning] " << message << std::endl;  break;  // 注意:这里没有处理新的日志级别,因为我们假设这是在添加新级别之前的代码  }  }  
};  // 客户端代码,使用日志记录器  
void useLogger(Logger& logger, LogLevel level, const std::string& message) {  logger.log(level, message);  
}  int main() {  ConsoleLogger consoleLogger;  useLogger(consoleLogger, LogLevel::Info, "This is an info message.");  useLogger(consoleLogger, LogLevel::Warning, "This is a warning message.");  // 现在,如果我们想添加一个新的日志级别“错误”,我们应该怎么做呢?  // 遵循开闭原则,我们不应该修改已有的ConsoleLogger类。  // 取而代之的是,我们可以扩展LogLevel枚举和ConsoleLogger类(在实际应用中)。  // 但由于这个例子是静态的,我们不能在这里动态地添加新的枚举值或修改类。  // 因此,这个例子实际上是不完整的,它只是为了说明如果我们能够修改代码,我们应该如何扩展它。  // 在真实场景中,我们可能会使用其他设计模式(如策略模式)或编程技术(如插件系统)来实现真正的开闭原则。  return 0;  
}

注意:上面的例子实际上并没有完全遵守开闭原则,因为我们没有展示如何不修改ConsoleLogger类来添加新的日志级别。在真实的项目中,我们可能会使用如下方法:

  1. 使用配置文件或数据库来定义日志级别,而不是硬编码在枚举中。
  2. 使用策略模式或插件架构来允许动态添加新的日志处理器。
  3. 利用反射或类似的机制来在运行时动态识别和调用相应的日志处理方法。

由于C++语言本身的静态特性,完全实现开闭原则可能需要一些高级技巧和额外的设计考虑。

违背开闭原则的C++例子2

假设我们没有使用上面的抽象基类Logger和虚函数,而是直接在ConsoleLogger类中实现了所有日志级别的处理逻辑。当需要添加新的日志级别时,我们就不得不修改ConsoleLogger类的代码。

#include <iostream>  
#include <string>  // 日志级别枚举(同上)  
enum class LogLevel { /* ... */ };  // 控制台日志记录器实现(没有使用抽象基类和虚函数)  
class ConsoleLogger {  
public:  void logInfo(const std::string& message) const {  std::cout << "[Info] " << message << std::endl;  }  void logWarning(const std::string& message) const {  std::cout << "[Warning] " << message << std::endl;  }  // 假设这里原本没有logError方法,现在我们需要添加它。  // 这将违反开闭原则,因为我们需要修改ConsoleLogger类的代码。  void logError(const std::string& message) const {  std::cout << "[Error] " << message << std::endl;  }  
};  // 客户端代码(直接使用具体的日志记录器类)  
int main() {  ConsoleLogger consoleLogger;  consoleLogger.logInfo("This is an info message.");  consoleLogger.logWarning("This is a warning message.");  // 现在我们需要添加对错误日志的支持,所以我们必须修改ConsoleLogger类来添加logError方法。  consoleLogger.logError("This is an error message."); // 这将违反开闭原则。  return 0;  
}

在这个例子中,当我们需要添加新的日志级别时,我们不得不修改ConsoleLogger类来添加新的logError方法。这就违反了开闭原则的要求。

为了完全遵守开闭原则,我们需要设计一种扩展性更好的日志系统。在C++中,我们可以通过使用抽象基类、虚函数以及可能的工厂模式来实现这一目标。

遵守开闭原则的C++例子

下面是一个改进的例子,展示了如何遵守开闭原则:

#include <iostream>  
#include <map>  
#include <string>  
#include <memory>  // 日志级别枚举  
enum class LogLevel {  Info,  Warning,  Error // 新添加的日志级别  
};  // 日志记录器抽象基类  
class Logger {  
public:  virtual ~Logger() = default;  virtual void log(LogLevel level, const std::string& message) const = 0;  // 其他可能的共享功能...  
};  // 控制台日志记录器  
class ConsoleLogger : public Logger {  
public:  void log(LogLevel level, const std::string& message) const override {  switch (level) {  case LogLevel::Info:  std::cout << "[Info] " << message << std::endl;  break;  case LogLevel::Warning:  std::cout << "[Warning] " << message << std::endl;  break;  case LogLevel::Error: // 处理新添加的日志级别  std::cout << "[Error] " << message << std::endl;  break;  // 其他可能的日志级别...  }  }  
};  // 日志记录器工厂类  
class LoggerFactory {  
public:  static std::unique_ptr<Logger> createLogger(const std::string& type) {  if (type == "console") {  return std::make_unique<ConsoleLogger>();  }  // 可以在这里扩展其他类型的日志记录器,而不需要修改已有的代码  throw std::invalid_argument("Invalid logger type");  }  
};  // 客户端代码  
int main() {  // 通过工厂创建一个控制台日志记录器  auto consoleLogger = LoggerFactory::createLogger("console");  // 使用日志记录器记录不同级别的日志信息  consoleLogger->log(LogLevel::Info, "This is an info message.");  consoleLogger->log(LogLevel::Warning, "This is a warning message.");  consoleLogger->log(LogLevel::Error, "This is an error message."); // 新添加的日志级别可以正常使用  return 0;  
}

在这个例子中,我们定义了一个抽象基类Logger和一个派生类ConsoleLogger,它们通过虚函数log来实现多态。这样,我们就可以在不修改ConsoleLogger类的情况下添加新的日志级别。我们还定义了一个LoggerFactory工厂类来创建不同类型的日志记录器,这使得我们的系统更加灵活和可扩展。

现在,如果我们想添加一个新的日志记录器类型(比如文件日志记录器),我们只需要创建一个新的类(比如FileLogger),继承自Logger,并实现log函数。然后,在LoggerFactory类中添加一个新的条件分支来创建FileLogger对象即可。整个过程不需要修改已有的代码,完全符合开闭原则的要求。

3. 里氏替换原则(Liskov Substitution Principle, LSP)

含义:在软件中,如果 S 是 T 的子类型,则程序中使用 T 类型的对象的地方都可以用 S 类型的对象来替换,而不会改变程序的期望行为。这要求子类型必须能够完全替代其父类型。

例子:假设我们有一个Vehicle基类和一个派生类BicycleVehicle类有一个StartEngine()方法。但是,自行车没有引擎,所以这个方法在Bicycle类中没有意义。

违反里氏替换原则的代码可能是这样的:

class Vehicle {  
public:  virtual void StartEngine() = 0;  // ... 其他通用方法  
};  class Bicycle : public Vehicle {  
public:  void StartEngine() override { /* 什么也不做,因为自行车没有引擎 */ }  // ... Bicycle特有的方法  
};

这个例子违反了里氏替换原则,因为Bicycle不能正确地实现VehicleStartEngine()方法。正确的做法是不让Bicycle继承自Vehicle,或者重新设计类层次结构以反映实际情况。例如,可以创建一个没有StartEngine()方法的更通用的基类(如Transport),并让VehicleBicycle都继承自这个基类。或者,如果继承关系确实存在,那么应该避免在基类中定义那些不能被子类正确实现的方法。

上面的例子展示了违反里氏替换原则的情况。为了遵循这个原则,我们需要重新设计类层次结构,确保子类型能够正确地替换父类型。

下面是一个遵循里氏替换原则的改进例子:

// 定义一个更通用的基类,不包含特定于有引擎车辆的方法  
class Transport {  
public:  virtual void Move() = 0; // 所有交通工具都可以移动  // ... 其他通用方法  
};  // Vehicle类继承自Transport,并添加与引擎相关的方法  
class Vehicle : public Transport {  
protected:  Engine engine; // 假设有一个Engine类表示引擎  
public:  void StartEngine() { /* 启动引擎的代码 */ }  void Move() override { /* 使用引擎移动的代码 */ }  // ... 其他与车辆相关的方法  
};  // Bicycle类也继承自Transport,但不包含StartEngine方法  
class Bicycle : public Transport {  
public:  void Move() override { /* 骑自行车移动的代码 */ }  // ... 其他与自行车相关的方法  
};  // 现在我们可以使用Transport指针或引用来操作Vehicle和Bicycle对象,  
// 而不需要担心调用不适合的方法(比如StartEngine)  
void UseTransport(Transport& transport) {  transport.Move(); // 无论是Vehicle还是Bicycle,这个方法都是安全的  // 注意:我们不能在这里调用StartEngine(),因为不是所有Transport都有引擎  
}

在这个改进的例子中,我们创建了一个更通用的基类Transport,它只包含所有交通工具共有的方法,比如Move()。然后,Vehicle类继承自Transport并添加了与引擎相关的方法,而Bicycle类也继承自Transport但没有引擎相关的方法。这样,我们就可以安全地使用Transport类型的引用来操作任何交通工具,而不用担心调用不适当的方法。

4. 接口隔离原则(Interface Segregation Principle, ISP)

含义:客户端不应该依赖于它不需要的接口。一个类对另一个类的依赖应该是最小的。这通常意味着将大接口拆分成更小、更具体的接口,这样客户端只需要知道和使用它们感兴趣的方法。

例子:假设我们有一个打印机接口,它包含了打印、扫描和传真等多种功能的方法。但是,有些打印机只支持打印功能。

违反接口隔离原则的代码可能是这样的:

// 一个包含多种功能的打印机接口  
class MultiFunctionPrinter {  
public:  virtual void Print(const Document& doc) = 0;  virtual void Scan(const Document& doc) = 0;  virtual void Fax(const Document& doc, const FaxInfo& info) = 0;  // ... 其他方法  
};  // 一个只支持打印功能的打印机类,但不得不实现所有接口方法  
class SimplePrinter : public MultiFunctionPrinter {  
public:  void Print(const Document& doc) override { /* 实现打印 */ }  void Scan(const Document& doc) override { /* 无法实现,可能抛出异常或什么都不做 */ }  void Fax(const Document& doc, const FaxInfo& info) override { /* 无法实现 */ }  // ... 其他方法的空实现或异常抛出  
};

遵循接口隔离原则的代码应该是这样的:

// 将功能拆分成不同的接口  
class Printer {  
public:  virtual void Print(const Document& doc) = 0;  // ... 其他与打印相关的方法  
};  class Scanner {  
public:  virtual void Scan(const Document& doc) = 0;  // ... 其他与扫描相关的方法  
};  class FaxMachine {  
public:  virtual void Fax(const Document& doc, const FaxInfo& info) = 0;  // ... 其他与传真相关的方法  
};  // 现在我们可以创建只支持打印功能的打印机类,而不需要实现其他无关的方法  
class SimplePrinter : public Printer {  
public:  void Print(const Document& doc) override { /* 实现打印 */ }  // ... 其他与打印相关的方法的实现  
};

在这个改进的例子中,我们将大接口MultiFunctionPrinter拆分成了三个小接口:PrinterScannerFaxMachine。这样,SimplePrinter类就只需要实现它真正支持的打印功能,而不需要关心扫描和传真等其他无关的功能。这减少了类之间的不必要依赖,提高了代码的可维护性和可扩展性。

5. 依赖倒置原则(Dependency Inversion Principle, DIP)

含义:高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

例子:假设我们有一个读取文件的类和一个处理文件内容的类。如果处理类直接依赖于具体的读取类,那么它就违反了依赖倒置原则。

违反依赖倒置原则的代码可能是这样的:

// 具体的读取类  
class FileReader {  
public:  std::string ReadFile(const std::string& path) {  // 实现文件读取  return "file content";  }  
};  // 处理类直接依赖于FileReader  
class FileProcessor {  
private:  FileReader reader;  
public:  void ProcessFile(const std::string& path) {  std::string content = reader.ReadFile(path);  // 处理文件内容  }  
};

遵循依赖倒置原则的代码应该是这样的:

// 定义读取文件的抽象接口  
class IReader {  
public:  virtual std::string Read(const std::string& source) = 0;  
};  // 具体的读取类实现抽象接口  
class FileReader : public IReader {  
public:  std::string Read(const std::string& path) override {  // 实现文件读取  return "file content";  }  
};  // 处理类依赖于抽象接口而不是具体实现  
class FileProcessor {  
private:  IReader* reader; // 使用指针或智能指针以便动态绑定  
public:  FileProcessor(IReader* rdr) : reader(rdr) {} // 通过构造函数注入依赖  void ProcessFile(const std::string& path) {  std::string content = reader->Read(path); // 使用接口方法  // 处理文件内容  }  
};

在这个改进的例子中,我们创建了一个抽象接口IReader,并让FileReader类实现这个接口。然后,我们将FileProcessor类的依赖从具体的FileReader类改为IReader接口。这样,我们就可以轻松地替换不同的读取实现,而不需要修改FileProcessor类的代码。

6. 迪米特原则(Law of Demeter)

含义:迪米特原则(也称为最少知识原则,Law of Demeter, LoD)是面向对象设计中的一个重要原则,它强调一个对象应当对其他对象保持最少的了解,使得系统各部分之间的耦合度降低。具体来说,一个类应该尽量减少与其他类的直接交互,只与它的直接朋友(即直接与之关联的对象)通信,而不是与“陌生人”通信。

例子:假设我们有一个简单的图形绘制系统,其中有一个Shape基类,以及两个派生类CircleRectangle。我们还有一个Drawer类,负责绘制这些形状。按照迪米特原则,Drawer类应该只与Shape接口交互,而不必关心具体是哪种形状。

遵守迪米特原则的例子(C++)

#include <iostream>  // Shape基类  
class Shape {  
public:  virtual void draw() const = 0;  
};  // Circle类  
class Circle : public Shape {  
public:  void draw() const override {  std::cout << "Drawing a circle." << std::endl;  }  
};  // Rectangle类  
class Rectangle : public Shape {  
public:  void draw() const override {  std::cout << "Drawing a rectangle." << std::endl;  }  
};  // Drawer类  
class Drawer {  
public:  void drawShape(const Shape& shape) {  shape.draw(); // 只调用Shape接口的方法  }  
};  int main() {  Circle circle;  Rectangle rectangle;  Drawer drawer;  drawer.drawShape(circle);  drawer.drawShape(rectangle);  return 0;  
}

在这个例子中,Drawer类只与Shape接口交互,它不需要知道具体是Circle还是Rectangle,因此遵守了迪米特原则。

违背迪米特原则的例子(C++)

#include <iostream>  // Circle类  
class Circle {  
public:  void draw() const {  std::cout << "Drawing a circle." << std::endl;  }  
};  // Rectangle类  
class Rectangle {  
public:  void draw() const {  std::cout << "Drawing a rectangle." << std::endl;  }  
};  // Drawer类,直接依赖于具体的Circle和Rectangle类  
class Drawer {  
public:  void drawCircle(const Circle& circle) {  circle.draw();  }  void drawRectangle(const Rectangle& rectangle) {  rectangle.draw();  }  
};  int main() {  Circle circle;  Rectangle rectangle;  Drawer drawer;  drawer.drawCircle(circle);  drawer.drawRectangle(rectangle);  return 0;  
}

在这个例子中,Drawer类直接与CircleRectangle类交互,这意味着它对这些具体类的实现有了过多的了解。如果未来需要添加新的形状或者修改现有形状的实现,Drawer类可能也需要进行相应的修改,这增加了系统的耦合度。通过引入Shape基类并使用多态,我们可以避免这种情况,从而遵守迪米特原则。

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

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

相关文章

Android 10.0 app获取当前已连接wifi列表ssid和密码功能实现

1.前言 在10.0的系统定制化开发中,在一些关于wifi的定制中,有产品需求app中要求获取当前连接wifi和密码功能,在系统原生wifi中 是禁止获取wifi连接的密码的,所以就需要对wifi模块进行一部分的修改,来满足app中获取wifi的ssid和密码功能,接下来就来 实现这个功能 如图:…

金融知识分享系列之:支撑阻力

金融知识分享系列之&#xff1a;支撑阻力 一、支撑阻力原理二、支撑阻力作用1.识别市场资金的预期2.作为入场和平仓的重要参考 三、寻找支撑阻力四、延伸思考五、支撑阻力总结 一、支撑阻力原理 支撑阻力核心要素&#xff1a; 锚定效应订单驱动 支撑阻力原理&#xff1a; 市…

爬虫系列-CSS基础语法

&#x1f308;个人主页&#xff1a;会编程的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” CSS全称层叠样式表 &#xff0c;主要用来定义页面内容展示效果的一门语言&#xff0c;HTML&#xff1a;页面骨架&#xff0c;素颜CSS&#xff1a;页面效果美化&#xff1a…

01分布式搜索引擎ES

分布式搜索引擎ES 1.初识elasticsearch1.1.了解ES1.2.倒排索引1.3.es的一些概念 2.索引库操作2.1.mapping映射属性2.2.索引库的CRUD 3.文档操作3.1.新增文档3.2.查询文档3.3.删除文档3.4.修改文档3.5.总结 4.RestAPI4.0.导入Demo工程4.1.创建索引库4.2.删除索引库4.3.判断索引库…

免费PDF转换和编辑工具 PDFgear 2.1.4

PDFgear是一款功能强大的 PDF 阅读及转换软件。 它支持多种文件格式的转换和编辑&#xff0c;同时还提供了丰富的功能模块&#xff0c;如签名、表单填写等&#xff0c;方便用户进行多样化的操作。 该软件界面简洁美观&#xff0c;操作简单易懂&#xff0c;适合不同层次的用户…

java static

1、static概念 &#xff08;1&#xff09;static 关键字是静态的意思&#xff0c;可以修饰成员方法&#xff0c;成员变量 &#xff08;2&#xff09;static 修饰的特点 ① 被类的所有对象共享&#xff0c;这也是我们判断是否使用静态关键字的条件。 ② 可以通过类名调用&…

机器学习-06-回归算法

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中回归算法&#xff0c;包括线性回归&#xff0c;岭回归&#xff0c;逻辑回归等部分。 参考 fit_transform,fit,transform区别和作用详解&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…

ActiViz三维场景的基本要素——相机

文章目录 前言一、基本属性和方法二、相机操作三、高级功能四、 示例代码和应用五、总结前言 在ActiViz中,vtkCamera是一个非常重要的类,用于定义观察场景的视角和位置。作为三维可视化的核心组件之一,vtkCamera决定了用户在三维场景中所看到的图像内容和视角,因此它对于呈…

突破编程_C++_STL教程( queue 的基础知识)

1 std::queue 概述 std::queue 是 C 标准模板库&#xff08;STL&#xff09;中的一种容器适配器&#xff0c;它提供了队列&#xff08;Queue&#xff09;这种数据结构的功能。队列是一种特殊的线性表&#xff0c;它只允许在表的前端&#xff08;front&#xff09;进行删除操作…

Linux运维_Bash脚本_编译安装Apache(httpd-2.4.54)

Linux运维_Bash脚本_编译安装Apache(httpd-2.4.54) Bash (Bourne Again Shell) 是一个解释器&#xff0c;负责处理 Unix 系统命令行上的命令。它是由 Brian Fox 编写的免费软件&#xff0c;并于 1989 年发布的免费软件&#xff0c;作为 Sh (Bourne Shell) 的替代品。 您可以在…

基于Java中的SSM框架实现在线通用旅游平台网站系统项目【项目源码+论文说明】计算机毕业设计

基于Java中的SSM框架实现在线通用旅游平台网站系统演示 摘要 近几年来&#xff0c;计算机网络的发展得到了飞速的提升&#xff0c;由此展开的一系列行业大洗牌也由此开始。早些年只是人们只是对于计算机和互联网有了些基础的认识&#xff0c;现在它正在悄悄的改变着我们生活的…

富格林:运用可信技巧揪出暗箱黑幕

富格林悉知&#xff0c;在伦敦金中&#xff0c;对于市场中的暗箱黑幕骗局投资者应该从容应对&#xff0c;利用可信技巧顺利盈利。在市场投资中&#xff0c;投资者需要不断的学习伦敦金基础知识&#xff0c;总结可信的做单方法&#xff0c;更要从以往案例分析受害原因正规预防。…

安装MySQL5.7.19 + 解决数据库乱码

文章目录 1.删除mysql服务 sc delete mysql2.解压到D:\mysql5.7下3.配置管理员环境变量4.D:\mysql5.7\mysql-5.7.19-winx64下创建my.ini1.创建文件2.文件内容 5.管理员打开cmd&#xff0c;切换到 D:\mysql5.7\mysql-5.7.19-winx64\bin6.输入 mysqld -install 安装mysql服务7.初…

网页无插件视频播放器,支持录像、截图、音视频播放,多路播放等,提供源码下载

前言 本播放器内部采用jessibuca插件接口&#xff0c;支持录像、截图、音视频播放等功能。播放器播放基于ws流&#xff0c;分屏操作支持1分屏、4分屏、6分屏、9分屏方式。 jessibuca工作原理是通过Emscripten将音视频解码库编译成Js&#xff08;WebAssembly&#xff0c;简称was…

Java集合Collection之LinkedList

LinkeList LinkedList&#xff08;双向链表&#xff09;是一种常见的线性数据结构&#xff0c;但是并不会按线性的顺序存储数据。它由一系列节点组成&#xff0c;每个节点包含数据部分和一个指向下一个节点的引用。相比于数组&#xff0c;链表具有动态大小、插入和删除效率高的…

css第一个元素first-child匹配失败原因

<div><p>1</p><h1>2</h1><span>3</span><span>4</span> </div> 1、p:first-child 匹配的是p元素&#xff0c;因为p元素是div的第一个子元素 2、h1:first-child 匹配不到任何元素&#xff0c;因为在这里h1是di…

【电气安全】ASCP电气防火限流式保护器/末端回路线路保护

为什么要使用电气防火限流式保护器&#xff1f; 应急管理部消防救援局统计&#xff0c;在造成电气火灾事故的原因中&#xff0c;最为主要的当为末端线路短路&#xff0c;在电气火灾事故中占比高达70%以上。如何效预防末端线路短路引发的电气火灾事故&#xff1f; 现阶段最为常…

T470 双电池机制

ThinkPad系列电脑牛黑科技双电池管理体系技术,你知道吗&#xff1f; - 北京正方康特联想电脑代理商 上文的地址 在放电情况下&#xff1a;优先让外置电池放电&#xff0c;当放到一定电量后开始让内置电池放电。 在充电情况下&#xff1a;优先给内置电池充电&#xff0c;当充…

Perl: Can‘t locate List/MoreUtils.pm in @INC

BUG: 运行perl 脚本时报错&#xff1a; Cant locate List/MoreUtils.pm in INC (INC contains: /opt/rh/devtoolset-7/root/usr/lib64/perl5/vendor_perl /opt/rh/devtoolset-7/root/usr/share/perl5/vendor_perl /public/home/bgi_wangbinhu/perl5/lib/perl5/5.16.3/x86_64-l…

航空实时监控

1、从Kafka中读取飞机数据&#xff0c;并进行清洗 此步骤在前面的“使用Spark清洗统计业务数据并保存到数据库中”任务阶段应该已经完成。如果没有完成&#xff0c;请参考源代码自行完成。核心类主要有三个&#xff1a;SparkStreamingApplication类、SparkUtil类和MapManager类…