• [技术干货] Java 连接操作FTP与SFTP 详细指南
    引言 在Java开发中,文件传输协议(FTP)和安全文件传输协议(SFTP)是处理文件传输的两种常见方式。FTP是标准的网络文件传输协议,而SFTP则在FTP基础上增加了安全层(SSH),提供了更加安全的文件传输方式。本文将详细介绍如何在Java中实现与FTP和SFTP服务器的连接,并深入讲解各种函数的用法,以及如何进行二次封装,以提升代码的可复用性和可维护性。  文章目录 引言 一、Java连接FTP的实现 1. 配置与依赖 2. 基本连接与文件操作 3. 常用函数详解 4. 二次封装的工具类 二、Java连接SFTP的实现 1. 配置与依赖 2. 基本连接与文件操作 3.常用函数详解 4. 二次封装的工具类 FTP工具类的级联创建目录封装 SFTP工具类的级联创建目录封装 使用注意事项 三、Java连接FTP与SFTP的对比 1. 安全性 2. 性能 3. 支持与兼容性 四、总结与个人看法 一、Java连接FTP的实现 FTP协议(File Transfer Protocol)是一种用于在网络中传输文件的标准协议。Java提供了丰富的库来实现FTP连接,其中最常用的是Apache Commons Net库。该库提供了一个FTPClient类,简化了与FTP服务器的交互。  1. 配置与依赖 在使用FTPClient类之前,首先需要在项目中引入Apache Commons Net库的依赖。在Maven项目中,可以通过在pom.xml文件中添加以下依赖项来实现:  <dependency>     <groupId>commons-net</groupId>     <artifactId>commons-net</artifactId>     <version>3.8.0</version> </dependency> 2. 基本连接与文件操作 使用FTPClient连接到FTP服务器的基本流程如下:  创建FTPClient对象。 使用connect方法连接到FTP服务器。 使用login方法进行身份验证。 使用相应的方法执行文件操作,如上传、下载、删除文件等。 使用logout方法注销。 最后关闭连接。 以下是一个简单的示例,展示了如何连接到FTP服务器并上传文件:  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;  public class FTPExample {     public static void main(String[] args) {         FTPClient ftpClient = new FTPClient();         try {             // 连接到FTP服务器             ftpClient.connect("ftp.example.com", 21);             // 登录到FTP服务器             ftpClient.login("username", "password");              // 设置文件传输类型为二进制文件             ftpClient.setFileType(FTP.BINARY_FILE_TYPE);              // 上传文件             try (InputStream inputStream = new FileInputStream("local/file/path")) {                 boolean done = ftpClient.storeFile("/remote/file/path", inputStream);                 if (done) {                     System.out.println("The file is uploaded successfully.");                 }             }              // 注销并关闭连接             ftpClient.logout();         } catch (IOException ex) {             ex.printStackTrace();         } finally {             try {                 if (ftpClient.isConnected()) {                     ftpClient.disconnect();                 }             } catch (IOException ex) {                 ex.printStackTrace();             }         }     } }  在这个示例中,ftpClient.connect用于连接FTP服务器,ftpClient.login进行身份验证,ftpClient.storeFile方法用于上传文件。需要注意的是,FTP传输的默认模式是ASCII,因此在上传二进制文件时,需要调用setFileType(FTP.BINARY_FILE_TYPE)来设置传输模式。  3. 常用函数详解 FTPClient类提供了丰富的功能,以下是一些常用的FTP操作函数:  连接与登录  connect(String hostname, int port):连接到指定的FTP服务器和端口。 login(String username, String password):使用用户名和密码登录FTP服务器。 logout():注销当前用户。 文件操作  storeFile(String remote, InputStream local):将本地文件上传到服务器。 retrieveFile(String remote, OutputStream local):从服务器下载文件到本地。 deleteFile(String pathname):删除服务器上的文件。 listFiles(String pathname):列出指定目录下的文件和子目录。 目录操作  makeDirectory(String pathname):在服务器上创建一个新目录。 removeDirectory(String pathname):删除服务器上的目录。 changeWorkingDirectory(String pathname):改变当前工作目录。 printWorkingDirectory():获取当前工作目录的路径。 连接管理  disconnect():断开与FTP服务器的连接。 isConnected():检查是否已连接到服务器。 4. 二次封装的工具类 为了提高代码的可复用性,可以将上述FTP操作封装到一个工具类中。这不仅简化了使用,还可以集中处理异常和日志记录等通用逻辑。  import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;  public class FTPUtil {     private FTPClient ftpClient;      public FTPUtil(String host, int port, String username, String password) throws IOException {         ftpClient = new FTPClient();         ftpClient.connect(host, port);         ftpClient.login(username, password);         ftpClient.setFileType(FTP.BINARY_FILE_TYPE);     }      public boolean uploadFile(String remoteFilePath, InputStream inputStream) throws IOException {         try {             return ftpClient.storeFile(remoteFilePath, inputStream);         } finally {             inputStream.close();         }     }      public boolean downloadFile(String remoteFilePath, OutputStream outputStream) throws IOException {         try {             return ftpClient.retrieveFile(remoteFilePath, outputStream);         } finally {             outputStream.close();         }     }      public boolean deleteFile(String filePath) throws IOException {         return ftpClient.deleteFile(filePath);     }      public void logoutAndDisconnect() throws IOException {         if (ftpClient.isConnected()) {             ftpClient.logout();             ftpClient.disconnect();         }     } }  这个FTPUtil类提供了基本的上传、下载、删除文件功能,并封装了连接和注销逻辑,简化了FTP操作的使用。  二、Java连接SFTP的实现 SFTP(SSH File Transfer Protocol)是基于SSH协议的文件传输协议,提供了更加安全的文件传输方式。Java中实现SFTP连接通常使用JSch库,这个库由JCraft开发,用于SSH2的Java实现。  1. 配置与依赖 在使用JSch库之前,同样需要在项目中引入依赖。在Maven项目中,添加以下依赖项:  <dependency>     <groupId>com.jcraft</groupId>     <artifactId>jsch</artifactId>     <version>0.1.55</version> </dependency> 2. 基本连接与文件操作 使用JSch连接到SFTP服务器的基本流程如下:  创建JSch对象。 使用getSession方法创建会话并进行身份验证。 使用connect方法连接到SFTP服务器。 使用ChannelSftp对象执行文件操作。 关闭连接。 以下是一个简单的示例,展示了如何连接到SFTP服务器并上传文件:  import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session;  import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties;  public class SFTPExample {     public static void main(String[] args) {         String SFTPHOST = "sftp.example.com";         int SFTPPORT = 22;         String SFTPUSER = "username";         String SFTPPASS = "password";         String SFTPWORKINGDIR = "/remote/path/";          Session session = null;         ChannelSftp channelSftp = null;          try {             JSch jsch = new JSch();             session = jsch.getSession(SFTPUSER, SFTPHOST, SFTPPORT);             session.setPassword(SFTPPASS);              // 配置StrictHostKeyChecking属性             Properties config = new Properties();             config.put("StrictHostKeyChecking", "no");             session.setConfig(config);              session.connect();              channelSftp = (ChannelSftp) session.openChannel("sftp");             channelSftp.connect();              // 上传文件             try (InputStream inputStream = new FileInputStream("local/file/path")) {                 channelSftp.put(inputStream, SFTPWORKINGDIR + "fileName");             }          } catch (Exception ex) {             ex.printStackTrace();         } finally {             if (channelSftp != null) {                 channelSftp.disconnect();             }             if (session != null) {                 session.disconnect();             }         }     } }  在这个示例中,JSch类用于管理SSH连接和会话,ChannelSftp类用于执行具体的SFTP操作。需要注意的是,在实际开发中,StrictHostKeyChecking属性建议设置为yes以增强安全性。  3.常用函数详解 ChannelSftp类提供了丰富的功能,用于实现SFTP操作。以下是一些常用的SFTP操作函数:  连接与会话管理  connect():连接到SFTP服务器。 disconnect():断开与SFTP服务器的连接。 getSession():获取当前连接的会话(Session)。 文件操作  put(InputStream src, String dst):将本地文件上传到SFTP服务器的目标路径。 get(String src, OutputStream dst):从SFTP服务器下载文件到本地。 rm(String path):删除服务器上的文件。 ls(String path):列出指定目录下的文件和子目录。 目录操作  cd(String path):切换到指定目录。 mkdir(String path):在SFTP服务器上创建新目录。 rmdir(String path):删除SFTP服务器上的目录。 权限与属性管理  chmod(int permissions, String path):修改文件或目录的权限。 chown(int uid, String path):修改文件或目录的所有者。 chgrp(int gid, String path):修改文件或目录的所属组。 stat(String path):获取文件或目录的属性。 4. 二次封装的工具类 为了更好地管理SFTP连接和操作,实现更强大的功能,如级联创建目录和其他操作,我们可以将常用功能封装到一个工具类中,提高代码的可读性和可维护性的同时,确保可以顺利处理复杂的文件系统操作。比如级联创建目录,在级联创建目录时,我们需要逐级检查每个目录是否存在,如果不存在则创建它。这在FTP和SFTP操作中都非常常见。  FTP工具类的级联创建目录封装 import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;  public class FTPUtil {     private FTPClient ftpClient;      public FTPUtil(String host, int port, String username, String password) throws IOException {         ftpClient = new FTPClient();         ftpClient.connect(host, port);         ftpClient.login(username, password);         ftpClient.setFileType(FTP.BINARY_FILE_TYPE);     }      // 上传文件     public boolean uploadFile(String remoteFilePath, InputStream inputStream) throws IOException {         try {             return ftpClient.storeFile(remoteFilePath, inputStream);         } finally {             inputStream.close();         }     }      // 下载文件     public boolean downloadFile(String remoteFilePath, OutputStream outputStream) throws IOException {         try {             return ftpClient.retrieveFile(remoteFilePath, outputStream);         } finally {             outputStream.close();         }     }      // 删除文件     public boolean deleteFile(String filePath) throws IOException {         return ftpClient.deleteFile(filePath);     }      // 级联创建目录     public boolean createDirectories(String remoteDirPath) throws IOException {         String[] directories = remoteDirPath.split("/");         String currentDir = "";         boolean dirExists = false;          for (String dir : directories) {             if (dir.isEmpty()) continue;             currentDir += "/" + dir;             dirExists = ftpClient.changeWorkingDirectory(currentDir);             if (!dirExists) {                 if (!ftpClient.makeDirectory(currentDir)) {                     throw new IOException("Failed to create directory: " + currentDir);                 }                 ftpClient.changeWorkingDirectory(currentDir);             }         }         return true;     }      // 删除目录(仅删除空目录)     public boolean deleteDirectory(String directoryPath) throws IOException {         return ftpClient.removeDirectory(directoryPath);     }      // 递归删除目录(包括子文件和子目录)     public boolean deleteDirectoryRecursively(String remoteDirPath) throws IOException {         FTPFile[] files = ftpClient.listFiles(remoteDirPath);         for (FTPFile file : files) {             String filePath = remoteDirPath + "/" + file.getName();             if (file.isDirectory()) {                 deleteDirectoryRecursively(filePath);             } else {                 deleteFile(filePath);             }         }         return deleteDirectory(remoteDirPath);     }      // 退出并断开连接     public void logoutAndDisconnect() throws IOException {         if (ftpClient.isConnected()) {             ftpClient.logout();             ftpClient.disconnect();         }     } }  SFTP工具类的级联创建目录封装 import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session;  import java.io.InputStream; import java.io.OutputStream; import java.util.Properties; import java.util.Vector;  public class SFTPUtil {     private ChannelSftp channelSftp;     private Session session;      public SFTPUtil(String host, int port, String username, String password) throws Exception {         JSch jsch = new JSch();         session = jsch.getSession(username, host, port);         session.setPassword(password);          Properties config = new Properties();         config.put("StrictHostKeyChecking", "no");         session.setConfig(config);         session.connect();          channelSftp = (ChannelSftp) session.openChannel("sftp");         channelSftp.connect();     }      // 上传文件     public void uploadFile(String remoteDir, String remoteFileName, InputStream inputStream) throws Exception {         channelSftp.cd(remoteDir);         channelSftp.put(inputStream, remoteFileName);     }      // 下载文件     public void downloadFile(String remoteFilePath, OutputStream outputStream) throws Exception {         channelSftp.get(remoteFilePath, outputStream);     }      // 删除文件     public void deleteFile(String remoteFilePath) throws Exception {         channelSftp.rm(remoteFilePath);     }      // 级联创建目录     public void createDirectories(String remoteDirPath) throws Exception {         String[] directories = remoteDirPath.split("/");         String currentDir = "";         for (String dir : directories) {             if (dir.isEmpty()) continue;             currentDir += "/" + dir;             try {                 channelSftp.cd(currentDir);             } catch (Exception e) {                 channelSftp.mkdir(currentDir);                 channelSftp.cd(currentDir);             }         }     }      // 删除目录(仅删除空目录)     public void deleteDirectory(String remoteDir) throws Exception {         channelSftp.rmdir(remoteDir);     }      // 递归删除目录(包括子文件和子目录)     public void deleteDirectoryRecursively(String remoteDirPath) throws Exception {         Vector<ChannelSftp.LsEntry> files = channelSftp.ls(remoteDirPath);         for (ChannelSftp.LsEntry entry : files) {             if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {                 String filePath = remoteDirPath + "/" + entry.getFilename();                 if (entry.getAttrs().isDir()) {                     deleteDirectoryRecursively(filePath);                 } else {                     deleteFile(filePath);                 }             }         }         deleteDirectory(remoteDirPath);     }      // 退出并断开连接     public void logoutAndDisconnect() {         if (channelSftp != null) {             channelSftp.disconnect();         }         if (session != null) {             session.disconnect();         }     } }  使用注意事项 级联目录创建:在处理复杂路径时,必须确保各级目录按顺序被创建。如果操作失败,应当立即停止并报告错误。 递归删除:在删除目录时,递归删除可以确保目录及其所有内容都被彻底删除。然而,必须注意,这是一项危险操作,可能会导致数据丢失,因此在执行之前应该进行仔细确认。 异常处理:在目录操作过程中,由于路径错误、权限不足或网络问题可能导致异常,应确保这些异常被捕获并妥善处理,以避免影响系统的稳定性。 三、Java连接FTP与SFTP的对比 FTP和SFTP在实际项目中各有应用场景。FTP由于其简单性和广泛支持,适用于一些对安全性要求不高的场景;而SFTP由于具备更高的安全性和数据加密特性,更适合用于涉及敏感数据的场景。  1. 安全性 SFTP在安全性方面具有明显的优势。它通过SSH协议加密数据传输,避免了数据在传输过程中的被窃听和篡改风险。相比之下,FTP采用明文传输,容易受到中间人攻击。 2. 性能 FTP通常在速度上略优于SFTP,因为它省去了加密和解密过程。不过,在现代硬件环境下,这种性能差异通常可以忽略不计。 3. 支持与兼容性 FTP在许多系统中得到了广泛的支持,几乎所有的操作系统和许多文件管理工具都支持FTP。而SFTP的支持相对较少,但在涉及安全要求的项目中,SFTP是更合适的选择。 四、总结与个人看法 通过本文,我们深入探讨了如何在Java中实现FTP和SFTP连接,并详细介绍了相关的函数用法和二次封装工具类的实现。对于两者的应用场景和差异,我们也进行了对比分析。  个人看法:在选择FTP还是SFTP时,应该根据项目的具体需求进行权衡。如果安全性是重中之重,SFTP无疑是首选。此外,随着互联网安全要求的日益提高,FTP的应用场景可能会逐渐减少。因此,在新项目中,建议优先考虑SFTP作为文件传输的解决方案。当然,在某些内网环境或临时性项目中,FTP的简单性和较低的配置成本也使其成为一个不错的选择。在处理文件传输任务时,封装常用的操作功能,不仅可以提高代码的复用性,还能减少开发过程中的重复劳动,提升项目的开发效率。对于级联操作和递归操作,虽然实现起来相对复杂,但这些功能对于许多实际场景是必不可少的,因此在开发中应当积极采用这些封装技术,确保系统的健壮性和灵活性。  希望这篇随笔能够帮助你更好地理解和掌握Java中FTP和SFTP的实现方法,并为实际项目提供一些有价值的参考。如果你在开发过程中有任何问题或需要进一步探讨,欢迎随时交流。 ————————————————                 原文链接:https://blog.csdn.net/QWERTYwqj/article/details/141160425 
  • [技术干货] Java常用的设计模式和设计原则
    1、Java常用的设计模式 总体来说设计模式分为三大类,共23种:  (1)创建型模式,共五种: Factory(工厂模式:简单工厂模式和抽象工厂模式)、Factory Method(工厂方法模式)、Singleton(单例模式)、Builder(建造者模式)、Prototype(原始模型模式)。  (2)结构型模式,共七种: Adapter(适配器模式)、Decorator(装饰器模式)、Proxy(代理模式)、Facade(外观模式)、Bridge(桥梁模式)、Composite(组合模式)、Flyweight(享元模式)。  (3)行为型模式,共十一种: Strategy(策略模式)、Template Method(模板方法模式)、Observer(观察者模式)、Iterator(迭代子模式)、Chain Of Responsibility(责任链模式)、Command(命令模式)、Memento(备忘录模式)、State(状态模式)、Visitor(访问者模式)、Mediator(中介者模式)、Interpreter(解释器模式)。  (4)其实还有两类:并发型模式和线程池模式。  2、Java遵循的设计原则 总原则:开闭原则(Open Close Principle,OCP) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等。  (1)单一职责原则(Single Responsibility Principle,SRP) 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。  (2)里氏替换原则(Liskov Substitution Principle,LSP) 面向对象设计的基本原则之一。 里氏替换原则中说,任何父类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。  (3)依赖倒转原则(Dependence Inversion Principle,DIP) 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。  (4)接口隔离原则(Interface Segregation Principle,ISP) 这个原则的意思是:每个接口中不应存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。  (5)迪米特原则(最少知道原则)(Law of Demeter,LOD或Least Knowledge Principle,LKP) 就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。  (6)合成复用原则(Composite Reuse Principle,CRP) 原则是尽量首先使用合成/聚合的方式,而不是使用继承。 ————————————————     原文链接:https://blog.csdn.net/jsc123581/article/details/81748124 
  • [技术干货] 面向对象设计模式:23种经典模式解析
    1.背景介绍 面向对象设计模式是一种软件设计的方法,它提供了一种抽象的方式来解决常见的软件设计问题。这种方法通过将问题分解为一组可重用的、可组合的对象来解决。这些对象可以表示实体、概念或抽象,并可以通过消息传递来交互。设计模式可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。  在本文中,我们将讨论23种经典的面向对象设计模式,并详细解释它们的定义、原理、优缺点和应用场景。这些设计模式可以分为三个类别:创建型模式、结构型模式和行为型模式。  2.核心概念与联系 2.1 创建型模式 创建型模式是一种用于创建对象的设计模式,它们提供了一种抽象的方式来创建对象,使得代码更加可维护和可扩展。创建型模式包括以下七种:  单例模式(Singleton) 工厂方法模式(Factory Method) 抽象工厂模式(Abstract Factory) 建造者模式(Builder) 原型模式(Prototype) 模板方法模式(Template Method) 代理模式(Proxy) 2.2 结构型模式 结构型模式是一种用于定义和组织类和对象的设计模式,它们提供了一种抽象的方式来组合类和对象,使得代码更加可维护和可扩展。结构型模式包括以下七种:  适配器模式(Adapter) 桥接模式(Bridge) 组合模式(Composite) 装饰模式(Decorator) 外观模式(Facade) 享元模式(Flyweight) 代理模式(Proxy) 2.3 行为型模式 行为型模式是一种用于定义对象之间的交互和行为的设计模式,它们提供了一种抽象的方式来描述对象之间的交互,使得代码更加可维护和可扩展。行为型模式包括以下十一种:  命令模式(Command) 策略模式(Strategy) 状态模式(State) 观察者模式(Observer) 中介模式(Mediator) 迭代子模式(Iterator) 访问者模式(Visitor) 备忘录模式(Memento) 责任链模式(Chain of Responsibility) 解释器模式(Interpreter) 3.核心算法原理和具体操作步骤以及数学模型公式详细讲解 在这个部分,我们将详细讲解每种设计模式的算法原理、具体操作步骤以及数学模型公式。由于篇幅限制,我们将只讨论其中的一部分设计模式。  3.1 单例模式 单例模式是一种创建型模式,它限制一个类只能有一个实例。这种模式通常用于管理全局资源,如数据库连接、文件输出等。  算法原理:单例模式使用一个静态变量来存储一个类的实例,并提供一个公共的静态方法来访问这个实例。当第一次访问这个方法时,它会创建一个新的实例,并将其存储在静态变量中。在后续的访问中,它会返回已存储的实例。  具体操作步骤:  在类中声明一个静态变量来存储实例。 在类中声明一个私有的构造函数,以防止外部创建新的实例。 在类中声明一个公共的静态方法,用于访问实例。 在静态方法中,检查静态变量是否已经存在实例。如果不存在,创建一个新的实例并将其存储在静态变量中。如果存在,返回已存储的实例。 数学模型公式:  $$ Singleton = {S \mid \forall i,j \in S, i \neq j \Rightarrow si = sj} $$  其中,$S$ 是单例集合,$si$ 和 $sj$ 是 $S$ 中的不同元素。  3.2 工厂方法模式 工厂方法模式是一种创建型模式,它提供了一个用于创建对象的接口,但让子类决定实例化哪个具体的类。这种模式通常用于创建不同类型的对象,而不需要知道它们的具体类。  算法原理:工厂方法模式定义了一个接口用于创建对象,并将实例化过程委托给子类。子类实现这个接口,并在其中调用具体的构造函数来创建对象。  具体操作步骤:  创建一个抽象的工厂类,包含一个用于创建对象的接口。 创建一个或多个具体的工厂类,继承抽象工厂类,并实现接口。 在具体工厂类中,实现创建对象的方法,调用具体的构造函数。 使用具体工厂类来创建对象。 数学模型公式:  $$ FactoryMethod = {F \mid \forall fi \in F, fi(c) = c_i} $$  其中,$F$ 是工厂方法集合,$fi$ 是 $F$ 中的一个方法,$c$ 是类的集合,$ci$ 是 $c$ 中的一个具体类。  4.具体代码实例和详细解释说明 在这个部分,我们将通过一个具体的代码实例来演示单例模式和工厂方法模式的使用。  4.1 单例模式 ```python class Singleton: _instance = None  def __new__(cls, *args, **kwargs):     if not cls._instance:         cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)     return cls._instance   def __init__(self):     self.value = 42 s1 = Singleton() s2 = Singleton() print(s1 is s2) # True ```  在这个例子中,我们定义了一个 Singleton 类,它使用一个静态变量 _instance 来存储实例。在 __new__ 方法中,我们检查静态变量是否已经存在实例,如果不存在,创建一个新的实例并将其存储在静态变量中。如果存在,返回已存储的实例。  4.2 工厂方法模式 ```python from abc import ABC, abstractmethod  class Animal(ABC): @abstractmethod def speak(self): pass  class Dog(Animal): def speak(self): return "Woof!"  class Cat(Animal): def speak(self): return "Meow!"  class AnimalFactory: @staticmethod def createanimal(animaltype): if animaltype == "Dog": return Dog() elif animaltype == "Cat": return Cat() else: raise ValueError("Invalid animal type")  dog = AnimalFactory.createanimal("Dog") cat = AnimalFactory.createanimal("Cat") print(dog.speak()) # Woof! print(cat.speak()) # Meow! ```  在这个例子中,我们定义了一个 Animal 抽象类和两个具体的子类 Dog 和 Cat。这些类实现了一个 speak 方法,用于表示不同的动物发出的声音。我们还定义了一个 AnimalFactory 类,它包含一个静态方法 create_animal,用于根据传入的字符串创建不同类型的动物。这个方法使用了工厂方法模式,因为它提供了一个用于创建对象的接口,但让子类决定实例化哪个具体的类。  5.未来发展趋势与挑战 面向对象设计模式已经成为软件开发的一部分,它们已经广泛应用于各种类型的软件系统。未来,我们可以预见以下几个方面的发展趋势和挑战:  随着软件系统的复杂性和规模的增加,设计模式将更加重要,因为它们可以帮助我们更好地组织和管理代码。 随着编程语言和框架的发展,设计模式将更加灵活和可扩展,以适应不同的应用场景。 随着人工智能和机器学习的发展,设计模式将更加关注如何构建可扩展、可维护的机器学习模型和系统。 随着云计算和分布式系统的发展,设计模式将更加关注如何构建高性能、高可用性的分布式系统。 随着安全性和隐私问题的加剧,设计模式将更加关注如何构建安全、隐私保护的软件系统。 6.附录常见问题与解答 在这个部分,我们将回答一些常见问题:  Q:设计模式是否适用于所有的软件项目?  A:不适用。设计模式是一种软件设计的方法,它们可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。但是,在某些情况下,使用设计模式可能会导致代码过于复杂和难以理解。因此,在选择使用设计模式时,需要权衡其优缺点。  Q:设计模式是否会限制我的创造力?  A:不会。设计模式是一种抽象的方式来解决常见的软件设计问题,它们可以帮助程序员更快地开发高质量的软件,并提高代码的可读性、可维护性和可扩展性。使用设计模式并不意味着限制你的创造力,而是提供了一种更高效、更结构化的方式来构建软件系统。  Q:如何选择适合的设计模式?  A:选择适合的设计模式需要考虑以下几个因素:  问题的具体性:设计模式应该与问题紧密相关,不应该过度设计。 问题的复杂性:更复杂的问题可能需要多个设计模式来解决。 设计模式的可读性和可维护性:选择易于理解和维护的设计模式。 设计模式的适用性:确保选定的设计模式适用于当前的技术栈和团队能力。 Q:设计模式是否会导致代码冗余?  A:可能。使用设计模式可能会导致代码冗余,因为设计模式通常包括一些重复的代码。但是,这种冗余通常是可以接受的,因为它可以提高代码的可读性、可维护性和可扩展性。在选择使用设计模式时,需要权衡其优缺点。 ————————————————                       原文链接:https://blog.csdn.net/universsky2015/article/details/137313201 
  • [技术干货] 面向对象的23种设计模式
    设计原则:高内聚低耦合,开闭原则。 两大基础设计原则 在说面向对象设计的六大原则之前,我们先来说下程序设计的原则:模块内高内聚,模块间低耦合。我们在面向对象时只需把类看成模块,那么就容易理解封装等了。  说是七大原则,这里我先提出来一个:对扩展开放,对修改关闭。 为啥这么说,因为我们都知道软件是要改的。对扩展开放保证了可以增加功能,像泛型啦这些。对修改关闭保证了像前的兼容性,jdk7兼容jdk6这样。所以开闭原则围绕软件的整个生命周期。  从基础原则出发,产生六个具体的原则: 1.单一职责(一个方法或一个类只做一件事,为了模块内高内聚) 2.迪米特法则(也叫最少知道原则,为了模块间低耦合) 3.里氏替换(就是继承原则,子类可以无缝替代父类。很好的符合了开闭原则) 4.依赖倒置(类之间的依赖通过接口实现,低耦合的同时对扩展开放) 5.接口隔离(即把单个复杂接口拆分为多个独立接口,与上条共同实现面向接口编程) 6.合成复用原则(即尽量使用合成/聚合的方式,而不是使用继承。主要为了防止继承滥用而导致的类之间耦合严重。记住只有符合继承原则时才用继承)  设计模式 我觉得程序员最好的沟通方式是代码,所以每个设计模式都是一个例子。所有例子都很方便,可以复制直接运行。因为对java熟悉,所以下面设计模式例子都是用java语言来实现的。  创建型模式(IOC:控制反转,就是创建分离的集大成) 1.Singleton:单例模式(全局只要一个实例) 2.Prototype:原型模式(通过拷贝原对象创建新对象) 3.Factory Method:工厂方法模式(对象创建可控,隐藏具体类名等实现解耦) 4.Abstract Factory:抽象工厂模式(解决对象与其属性匹配的工厂模式) 5.Builder:建造者模式(封装降低耦合,生成的对象与构造顺序无关) 创建型模式的五种有各自的使用环境,单例和原型比较简单就不说了,工厂方法模式和建造者模式,都是封装和降低耦合有啥不同呢,其实工厂方法关注的是一个类有多个子类的对象创建(汽车类的各种品牌),而建造者模式关注的是属性较多的对象创建(能达到过程无关)。而抽象工厂模式关注的是对象和属性及属性与属性的匹配关系(如奥迪汽车与其发动机及空调的匹配)。  结构型模式(对象的组成以及对象之间的依赖关系) 1.Adapter:适配器模式(适配不同接口和类,一般解决历史遗留问题) 2.Decorator:装饰器模式(比继承更灵活,可用排列组合形成多种扩展类) 3.Proxy:代理模式(可以给类的每个方法增加逻辑,如身份验证) 4.Facade:外观模式(对模块或产品的封装,降低耦合) 5.Bridge:桥接模式(就是接口模式,抽象与实现分离) 6.Plyweight:享元模式(相同对象的重用) 7.Composite:组合模式(整体和部分相同时,如文件夹包含文件夹) 我们可以看到适配器模式、装饰器模式、代理模式都可以用包装对象来实现(把对象作为一个属性放在用的对象里),所以模式关注的并不是实现,而是解决的问题。模式更多体现的是类与类之间的逻辑关系,比如代理模式和装饰器模式很像。但从字面就知道,代理是访问不了实际工作对象的,这是他们的区别。  行为型模式(即方法及其调用关系) 1.Strategy:策略模式(提供功能不同实现方式,且实现可选) 2.Template Method:模板方法模式(相同流程用一个模板方法) 3.Observer:观察者模式(用订阅-发布实现的被观察者变化时回调) 4.Iterator:迭代器模式(一种内部实现无关的集合遍历模式) 5.Chain of Responsibility:责任链模式(事件处理的分层结构产生的责任链条) 6.Command:命令模式(将命令者与被命令者分离) 7.Memento:备忘录模式(需要撤销与恢复操作时使用) 8.State:状态模式 (当对象两种状态差别很大时使用) 9.Visitor:访问者模式 (当对同一对象有多种不同操作时使用) 10.Mediator:中介者模式(以中介为中心,将网状关系变成星型关系) 11.Interpreter:解释器模式(常用于纯文本的表达式执行)  写完设计模式之后感觉设计模式更多的一种逻辑关系,如果代码中有这种逻辑关系就可以用了。记得需要时候再用,不能为了设计模式而设计模式。没有什么就是好的,最主要用起来舒服吧。 ———————————————— 原文链接:https://blog.csdn.net/wanyouzhi/article/details/77248710 
  • [技术干货] 常用的设计模式
    什么是设计模式? 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。面向对象设计模式通常以类或者对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。  设计模式的基本原则 1. 依赖倒置原则  高层模块不应该依赖低层模块,二者都应该依赖抽象; 抽象不应该依赖具体实现,具体实现应该依赖于抽象;  举个例子,自动驾驶系统公司是高层,汽⻋生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽⻋生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。  2. 开放封闭原则 一个类应该对扩展开放,对修改关闭。也就是对一个类尽量做扩展而不是修改,扩展一般是用继承或者组合的方式。 3. 面向接口编程 不将变量类型声明为某个特定的具体类,而是声明为某个接口。比如某个类成员是另一个类的具体对象,那么应该改成指向这个类的指针,因为根据多态原理,这个类指针实际上成为了一个接口。 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。  4. 封装变化点 将稳定点和变化点分离,扩展修改变化点;让稳定点与变化点的实现层次分离。 5.  单一职责原则 一个类应该仅有一个引起它变化的原因。 6. 里式替换原则 子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可 能出现错误;覆盖了父类方法却没实现父类方法的职责。也就是子类如果声明了父类存在的函数 就要把功能实现完全,父类有的子类也一定要有。 7. 接口隔离原则 不应该强迫客户依赖于他们不用的方法。 一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责。 所以要正确使用public,protected,private这三种权限。 8. 对象组合优于类继承 继承耦合度高,组合耦合度低(继承不能换爹,组合可以换爹)。 如何找到设计模式 从重构中获得:  1. 静态转变为动态 2. 早绑定转为晚绑定 3. 继承转为组合 4. 编译时依赖转变为运行时依赖 5. 紧耦合转变为松耦合 掌握设计模式的原则比具体的设计模式更重要,只有当你对业务非常熟悉的时候,才能信手拈来的写出适合的设计模式。  常见的设计模式和使用场景 1. 模板模式 定义一个操作中的算法的⻣架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不 改变一个算法的结构即可重定义该算法的某些特定步骤。  下面来看一个例子:  class IGame { public:     Play()     {         Process1();         Process2();         Process3();         Process4();     } protected:     virtual void Process1(){}     virtual void Process2(){}     virtual void Process3(){}     virtual void Process4(){} };   class Game1:public IGame { protected:     virtual void Process2(){}     virtual void Process4(){} };   class Game2:public IGame { protected:     virtual void Process1(){}     virtual void Process3(){} };  某个游戏IGame类有固定的流程,然后Game1和Game2分别继承自IGame,但是在具体某个步骤上各有不同,因此将Process1~4写成虚函数,由各个子类自己去实现。  这样写的前提当然是主框架流程是固定的,Play函数对外开放给用户使用,但是具体流程的函数是protected,提供子类去修改。这体现了接口隔离原则。 流程的接口在基类都抽象成了虚函数,由各个子类具体实现,这体现了依赖倒置原则。  2. 观察者模式 观察者模式在分布式架构中应用广泛,比如actor框架,skynet,redis,zoomkeeper,订阅和发布等。 观察者模式定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有 依赖于它的对象都得到通知并自动更新。  // 终端基类 class ITerminal  { public:     virtual void show(int data){} };   class DataCenter { public:     void Attach(ITerminal* ob){} // 注册绑定     void Detach(ITerminal* ob){} // 解绑     void Notify()     {         for(auto it = obs.begin(); it != obs.end(); ++it)         {             it->show();         }     } protected:     virtual int GetData(){return  0;} // 获取数据 private:     std::vector<ITerminal*> obs; };   class Terminal1:public ITerminal { public:     void show(int data){} } class Terminal2:public ITerminal { public:     void show(int data){} }  上面是一个发布和订阅的简单例子,假设有个数据中心提供数据给不同的终端展示,数据中心是一个稳定点,不同的终端设备是变化点,因此将终端封装成基类,提供一个展示的接口,不同的设备继承去实现不同的展示方式。  数据中心用一个vector或者set把注册过来的终端记录起来,需要展示的时候通过notify接口逐个通知展示。这样数据中心不必关注具体有哪些设备订阅了数据,是否订阅的操作交给了各个终端设备上。  3. 策略模式 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用 它的客户程序而变化。  策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法 之间进行切换; 策略模式消除了条件判断语句;就是在解耦合; 充分体现了开闭原则;单一职责; 其本质是分离算法,选择实现。  来看一个简单的例子,假设某电商网站一件商品在不同节假日的价格算法使不同的,可能会这么写:  enum VacationEnum {     chunjie, // 春节     wuyi; // 五一     qixi; // 七夕     guoqing; // 国庆 };   class Context { };   class Promotion {     VacationEnum vac; public:     double calc()     {         if(vac == chunjie){}         else if(vac == wuyi){}         else if(vac == qixi){}         else if(vac == guoqing){}     } };  这样写并不符合设计模式的单一职责原则和开放封闭原则。所以可以改成这样:  class Context {};   // 变化的  扩展的 class BaseVac { public:     virtual calc(Context &ctx){} }; class Vacchunjie: public BaseVac { public:     virtual calc(Context &ctx){} }; class Vacwuyi: public BaseVac { public:     virtual calc(Context &ctx){} }; class Vacxiqi: public BaseVac { public:     virtual calc(Context &ctx){} };   // 稳定的 单一职责的 class Promotion { public:     Promotion(BaseVac  *ss):s(ss){}     double calc(Context &ctx)     {         s->calc(ctx);     } private:     BaseVac  *s; };   int main() {     BaseVac *v = new Vacxiqi();     Promotion *p = new Promotion(v);          return 0; }  把变化点(不同节假日的不同算法)隔离出去,只保留单一的职责:获取价格。  这样做非常符合设计模式的原则,但是前提是对业务非常熟悉,因为别人来扩展维护的话,必须先了解之前已有的扩展有哪些。  4. 责任链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成 一条链,并沿着这条链传递请求,直到有一个对象处理它为止。  这个模式的一些要点:  a.解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;  b.责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;  c.责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;  d.充分体现了单一职责原则;将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展, 同时职责顺序也可以任意扩展;  本质是分离职责,动态组合。  责任链模式是比较过时的,在c语言里用的比较多,c++里有更好的数据结构(list)可以代替这个模式。  来看一个具体例子,公司请假审批规则是小于3天只给人事审批,大于3天且小于5天给经理审批,5天以上的给老板审批。下面是第一个版本的代码:  class Context { public:     std::string name;     int day; };   class LeaveRequest { public:     void HandleRequest(const Context &cxt)     {         if(cxt.day < 3)         {             HandleByHR(cxt);         }         else if(cxt.day < 5);         {             HandleByMgr(cxt);         }         else         {             HandleByBoss(cxt);         }              } private: // 接口隔离原则     void HandleByHR(const Context &cxt){}     void HandleByMgr(const Context &cxt){}     void HandleByBoss(const Context &cxt){} };  怎么重构这个代码,从封装变化点的原则来看,先把HandleRequest里的各种条件拆分出来,然后新增一个类成员指针指向上一级,子类只需要判断自己是否应该处理,不能处理的就通过指针转移到上级去处理。  class Context { public:     std::string name;     int day; };   class IHandler { public:     virtual ~ IHandler(){}     void SetNext(IHandler *n){bext = n;}     virtual bool HandleRequest(const Context &cxt)     {         if(CanHandle(cxt))         {         }         else if(GetNext())         {             GetNext()-> HandleRequest(cxt);         }         else         {             // error         }     } protected:     virtual bool CanHandle(const Context &cxt){}     IHandler * GetNext(){return next;} private:     IHandler *next; };   class HandleByMainProgram : public IHandler { protected:     virtual bool HandleRequest(const Context &ctx){         //     }     virtual bool CanHandle() {         //     } };   class HandleByProjMgr : public IHandler { protected:     virtual bool HandleRequest(const Context &ctx){         //     }     virtual bool CanHandle() {         //     } }; class HandleByBoss : public IHandler { public:     virtual bool HandleRequest(const Context &ctx){         //     } protected:     virtual bool CanHandle() {         //     } };   int main () {     IHandler * h1 = new MainProgram();     IHandler * h2 = new HandleByProjMgr();     IHandler * h3 = new HandleByBoss();     h1->SetNextHandler(h2);     h2->SetNextHandler(h3);       Context ctx;     h1->handle(ctx);     return 0; }  把不同部门的职责拆出来作为子类继承,主流程封装进基类里,上面这个版本同时使用了模板模式和责任链模式。  子类的接口都是protected的,这体现了接口隔离原则和依赖倒置原则。  责任链模式可以变形成功能链模式,比如nginx阶段处理,不同之处在于责任链每次只有一个模块会处理,功能链是按顺序执行不同的模块。  5. 装饰器模式 动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生成子类更为灵活。  这个模式看起来会和责任链模式很像。  通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。 不是解决“多子类衍生的多继承”问题,而是解决“父类在多个方向上的扩展功能”问题; 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能;  装饰器模式本质是动态组合。  举个例子,普通员工有销售奖金,累计奖金,部⻔经理除此之外还有团队奖金;后面可能会添加环比增⻓奖 金,同时可能针对不同的职位产生不同的奖金组合。那么代码可以这样写:  class Context { public:     bool isMgr;     // User user;     // double groupsale; };   class Bonus { public:     double CalcBonus(Context &ctx) {         double bonus = 0.0;         bonus += CalcMonthBonus(ctx);         bonus += CalcSumBonus(ctx);         if (ctx.isMgr) {             bonus += CalcGroupBonus(ctx);         }         return bonus;     } private:     double CalcMonthBonus(Context &ctx) {         double bonus/* = */;         return bonus;     }     double CalcSumBonus(Context &ctx) {         double bonus/* = */;         return bonus;     }     double CalcGroupBonus(Context &ctx) {         double bonus/* = */;         return bonus;     } };   int main() {     Context ctx;     // 设置 ctx     Bonus *bonus = new Bonus;     bonus->CalcBonus(ctx); }  重构成装饰器模式后的代码:  class Context { public:     bool isMgr;     // User user;     // double groupsale; };   // 试着从职责出发,将职责抽象出来 class CalcBonus {     public:     CalcBonus(CalcBonus * c = nullptr) {}     virtual double Calc(Context &ctx) {         return 0.0; // 基本工资     }     virtual ~CalcBonus() {}   protected:     CalcBonus* cc; };   class CalcMonthBonus : public CalcBonus { public:     CalcMonthBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double mbonus /*= 计算流程忽略*/;          return mbonus + cc->Calc(ctx);     } };   class CalcSumBonus : public CalcBonus { public:     CalcSumBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double sbonus /*= 计算流程忽略*/;          return sbonus + cc->Calc(ctx);     } };   class CalcGroupBonus : public CalcBonus { public:     CalcGroupBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double gbnonus /*= 计算流程忽略*/;          return gbnonus + cc->Calc(ctx);     } };   class CalcCycleBonus : public CalcBonus { public:     CalcGroupBonus(CalcBonus * c) : cc(c) {}     virtual double Calc(Context &ctx) {         double gbnonus /*= 计算流程忽略*/;          return gbnonus + cc->Calc(ctx);     } };   int main() {     // 1. 普通员工     Context ctx1;     CalcBonus *base = new CalcBonus();     CalcBonus *cb1 = new CalcMonthBonus(base);     CalcBonus *cb2 = new CalcSumBonus(cb1);     cb2->Calc(ctx1);     // 2. 部门经理     Context ctx2;     CalcBonus *cb3 = new CalcGroupBonus(cb2);     cb3->Calc(ctx2); }  在计算奖金的形式上,有点像递归,因为构造函数里设置的基类成员是给用户自己设置的,可以 灵活组合。而不是第一个版本那样用ifelse去判断是否要添加奖金 ,这样也很容易扩展和修改。  打个比方,责任链的模式看起来就像一个链表的结构,而装饰器模式看起来像一个俄罗斯套娃。  6. 单例模式 保证一个类仅有一个实例,并提供一个该实例的全局访问点。  先了解一个知识点,由C/C++编译的程序占用的内存分为以下几个部分:  1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。  2、堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。  3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。  4、文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放  5、程序代码区:存放函数体的二进制代码。  下面来看看几种实现单例模式的例子。  版本一:     #include <mutex>   class Singleton  {      // 懒汉模式 lazy load public:     static Singleton * GetInstance()      {         //std::lock_guard<std::mutex> lock(_mutex); // 这里加锁粒度太大 切换线程开销大          if (_instance == nullptr)          {             std::lock_guard<std::mutex> lock(_mutex); // 在这里加锁             if (_instance == nullptr) // 双重判断             {                 _instance = new Singleton();                 atexit(Destructor);             }         }         return _instance;     }   private:     static void Destructor()      {         if (nullptr != _instance)          {             delete _instance;             _instance = nullptr;         }      }     Singleton(){} //构造     Singleton(const Singleton &cpy){} //拷⻉构造      Singleton& operator=(const Singleton&) {}      static Singleton * _instance;     static std::mutex _mutex; };   Singleton* Singleton::_instance = nullptr;//静态成员需要初始化  std::mutex Singleton::_mutex; //互斥锁初始化  这个例子有几个需要注意的点,第一个是加锁的粒度,当单例指针是空的时候再去加锁,减少锁粒度可以减少线程切换的开销。第二个是双重判断,防止多线程重入导致多次分配的问题。第三这是个懒汉模式,用到的时候才决定分配初始化。  但是这个例子有个问题。  c++的new操作里分为了三个步骤:分配内存,调用构造函数,赋值操作。在多线程环境下,cpu会进行指令重排,前面三个步骤可能执行起来是132的顺序。所以可能会出现某个线程判断单例指针不为空,但是还没调用构造函数,就立即返回,最后导致程序崩溃。所以上面这个例子还是有问题的。(如果在判断前就加锁是没问题的,代价是增加了开销)  版本二:    #include <mutex> #include <atomic>   class Singleton  { public:     static Singleton * GetInstance()      {         Singleton* tmp = _instance.load(std::memory_order_relaxed); //取出原子对象               std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障          if (tmp == nullptr)          {             std::lock_guard<std::mutex> lock(_mutex);             tmp = _instance.load(std::memory_order_relaxed);             if (tmp == nullptr)              {                 tmp = new Singleton;                       std::atomic_thread_fence(std::memory_order_release);//释放内存屏障                 _instance.store(tmp, std::memory_order_relaxed);// 赋值给原子对象                 atexit(Destructor);             }         }         return tmp;      }   private:     static void Destructor()      {         Singleton* tmp = _instance.load(std::memory_order_relaxed);         if (nullptr != tmp)          {             delete tmp;          }     }     Singleton(){}     Singleton(const Singleton&) {}     Singleton& operator=(const Singleton&) {}     static std::atomic<Singleton*> _instance;     static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance;//原子对象 静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化 // g++ Singleton.cpp -o singleton -std=c++11  上面代码用c++11特性内存屏障来解决cpu指令重排的问题。  版本三:  class Singleton { public:   ~Singleton(){}   static Singleton& GetInstance()    {     static Singleton instance;     return instance;   } private:   Singleton(){}   Singleton(const Singleton&) {}   Singleton& operator=(const Singleton&) {} }; // g++ Singleton.cpp -o singleton -std=c++11 这个版本需要在c++11或者更高级版本的编译后才能用。  c++11 magic static 特性:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这就解决了多线程场景带来的问题。  该版本的优点:  a. 利用静态局部变量特性,延迟加载;  b. 利用静态局部变量特性,系统自动回收内存,自动调用析构函数;  c. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作;  d. c++11 静态局部变量初始化时,具备线程安全;  版本四:    template<typename T> class Singleton  { public:     static T& GetInstance()      {         static T instance; // 这里要初始化DesignPattern,需要调用DesignPattern 构造函数,同时会调用父类的构造函数。          return instance;     }    protected:     virtual ~Singleton() {}     Singleton() {} // protected修饰构造函数,才能让别人继承 Singleton(const Singleton&) {}     Singleton& operator =(const Singleton&) {} };   class DesignPattern : public Singleton<DesignPattern>  {     friend class Singleton<DesignPattern>; // friend 能让 Singleton<T> 访 问到 DesignPattern构造函数 private:     DesignPattern(){}     DesignPattern(const DesignPattern&) {}     DesignPattern& operator=(const DesignPattern&) {} }  这个版本将单例模式封装成了模板,需要注意两个地方:  a.单例模板类的构造函数用protected修饰是为了它本身能被继承。  b. 单例模板里的GetInstance接口要获取类型T的构造函数,因此继承这个模板的类必须声明模板类为友元。  7. 工厂方法模式 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化 延迟到子类。  一般这些业务会使用工厂模式:解决创建过程比较复杂,希望对外隐藏这些细节;比如连接池,线程池; 隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系;  线程池的初始化需要由机器的内核数来决定线程数量,但是用户并不需要关心内核数,用户只需要获取到可用的线程就行。因此可以用工厂模式。  其本质是延迟到子类来选择实现;  举个例子,实现一个导出数据的接口,让客户选择数据的导出方式:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   // =====1 int main() {     std::string choose/* = */;     if (choose == "txt") {         IExport *e = new ExportTxt();         e->Export("hello world");     } else if (choose == "json") {         IExport *e = new ExportJson();         e->Export("hello world");     } else if (choose == "xml") {         IExport *e = new ExportXml();         e->Export("hello world");     } }  对于数据导出有多种不同格式的类,在业务代码层面上,第一个版本里需要使用者区分判断多种格式,我们希望把这种复杂的创建操作封装起来,因此创建一个工厂类,这个类绑定了导出数据的操作和指向具体数据格式的类成员,使用者只需要创建某个具体的工厂子类,调用导出的接口就完成任务了。  重构后的代码:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportCSV : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class IExportFactory { public:     IExportFactory() {         _export = nullptr;     }     virtual ~IExportFactory() {         if (_export) {             delete _export;             _export = nullptr;         }     }     bool Export(const std::string &data) {         if (_export == nullptr) {             _export = NewExport();         }         return _export->Export(data);     } protected:     virtual IExport * NewExport(/* ... */) = 0; private:     IExport* _export; };   class ExportXmlFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportXml();         // 可能之后有什么操作         return temp;     } }; class ExportJsonFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportJson;         // 可能之后有什么操作         return temp;     } }; class ExportTxtFactory : public IExportFactory { protected:     IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportTxt;         // 可能之后有什么操作         return temp;     } };   class ExportCSVFactory : public IExportFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportCSV;         // 可能之后有什么操作         return temp;     } };   int main () {     IExportFactory *factory = new ExportTxtFactory();     factory->Export("hello world");     return 0; }  8. 抽象工厂模式 提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。  抽象工厂类是基于工厂类实现的,实际上是一回事,抽象工厂类拥有更多的更复杂的操作接口,但是对于使用者来说,仍然只需要创建时指定某个子工厂类,无需关心具体的操作内容。  举个例子,实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式:  #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public:     virtual bool Export(const std::string &data) = 0;     virtual ~IExport(){} };   class ExportXml : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportJson : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportTxt : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class ExportCSV : public IExport { public:     virtual bool Export(const std::string &data) {         return true;     } };   class IImport { public:     virtual bool Import(const std::string &data) = 0;     virtual ~IImport(){} };   class ImportXml : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportJson : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportTxt : public IImport { public:     virtual bool Import(const std::string &data) {         return true;     } };   class ImportCSV : public IImport { public:     virtual bool Import(const std::string &data) {         // ....         return true;     } };   class IDataApiFactory { public:     IDataApiFactory() {         _export = nullptr;         _import = nullptr;     }     virtual ~IDataApiFactory() {         if (_export) {             delete _export;             _export = nullptr;         }         if (_import) {             delete _import;             _import = nullptr;         }     }     bool Export(const std::string &data) {         if (_export == nullptr) {             _export = NewExport();         }         return _export->Export(data);     }     bool Import(const std::string &data) {         if (_import == nullptr) {             _import = NewImport();         }         return _import->Import(data);     } protected:     virtual IExport * NewExport(/* ... */) = 0;     virtual IImport * NewImport(/* ... */) = 0; private:     IExport *_export;     IImport *_import; };   class XmlApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportXml;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportXml;         // 可能之后有什么操作         return temp;     } };   class JsonApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportJson;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportJson;         // 可能之后有什么操作         return temp;     } }; class TxtApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportTxt;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportTxt;         // 可能之后有什么操作         return temp;     } };   class CSVApiFactory : public IDataApiFactory { protected:     virtual IExport * NewExport(/* ... */) {         // 可能有其它操作,或者许多参数         IExport * temp = new ExportCSV;         // 可能之后有什么操作         return temp;     }     virtual IImport * NewImport(/* ... */) {         // 可能有其它操作,或者许多参数         IImport * temp = new ImportCSV;         // 可能之后有什么操作         return temp;     } };   int main () {     IDataApiFactory *factory = new CSVApiFactory();     factory->Import("hello world");     factory->Export("hello world");     return 0; }  9. 适配器模式 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起 工作的那些类可以一起工作。  原来的接口是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接口,让原来的接 口继续保持稳定,在子类通过组合的方式来扩展功能。  其本质是转换匹配,复用功能。  举个日志系统的例子,原来是通过写磁盘的方式进行存储,后来因为查询不便,需要额外添加往数据库写日志 的功能(写文件和数据库并存):  #include <string> #include <vector> using namespace std;   class LogSys { public:     LogSys() {}     void WriteLog(const vector<string> &) {         // ... 日志id 时间戳 服务器id 具体日志内容 roleid     }     vector<string>& ReadLog() {         // ...         vector<string> data /* = ...*/;         return data;     } };   class DB;  // 面向接口编程 而不是具体类 强依赖  耦合性高  mysql mongo   class LogSysEx : public LogSys { public:     LogSysEx(DB *db) : _db(db) {}       void AddLog(const vector<string> &data) {         LogSys::WriteLog(data);         /*             这里调用 _db 的方法将 data 数据存储到数据库         */     }       void DelLog(const int logid) {         vector<string>& data = LogSys::ReadLog();         // 从 vector<string> 中删除 logid的日志         LogSys::WriteLog(data);         // 调用 _db 的方法将 logid的日志删除     }       void UpdateLog(const int logid, const string &udt) {         vector<string>& data = LogSys::ReadLog();         // 从 vector<string> 中更新 logid的日志 udt         LogSys::WriteLog(data);         // 调用 _db 的方法将 logid的日志更改     }       string& LocateLog(const int logid) {         vector<string>& data = LogSys::ReadLog();         string log1 /* = from log file*/;         string log2 /* = from db */;         string temp = log1 + ";" + log2;         return temp;     }      private:     DB* _db; };  咱就是说在对一个类进行扩展修改的时候,尽量不要动原来的代码,而是通过继承组合的方式创建新的类,在这个新的类里实现新功能和包含旧功能。  10. 代理模式 代理模式是为其他对象提供一种代理以控制对这对象的访问。  比如远程代理(隐藏一个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护 代理(在代理前后做额外操作,权限管理,引用计数等); 在分布式系统中,actor模型(skynet)等会常用到代理模式。  其本质是控制对象访问。  举个例子,在有些系统中,为了某些对象的纯粹性,只进行了功能相关封装(稳定点),后期添加了其他功能 需要对该对象进行额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进行修改,这样 会让稳定点也变得不稳定),可以抽象一层代理层:    class ISubject { public:     virtual void Handle() = 0;     virtual ~ISubject() {} };   // 该类在当前进程,也可能在其他进程当中 class RealSubject : public ISubject { public:     virtual void Handle() {         // 只完成功能相关的操作,不做其他模块的判断     } };   // 在当前进程当中  只会在某个模块中使用 class Proxy1 : public ISubject { public:     Proxy1(ISubject *subject) : _subject(subject) {}     virtual void Handle() {         // 在访问 RealSubject 之前做一些处理         //if (不满足条件)         //    return;         _subject->Handle();         count++;         // 在访问 RealSubject 之后做一些处理     } private:     ISubject* _subject;     static int count; }; int Proxy1::count = 0;   // 在分布式系统当中  skynet actor class Proxy2 : public ISubject { public:     virtual void Handle() {         // 在访问 RealSubject 之前做一些处理                  // 发送到数据到远端  网络处理  同步非阻塞 ntyco c协程         //IResult * val = rpc->call("RealSubject", "Handle");           // 在访问 RealSubject 之后做一些处理     } private:     /*void callback(IResult * val) {         // 在访问 RealSubject 之后做一些处理     }*/ };  上面代码中proxy类可以继承ISubject类也可以不继承,写成继承的样式是为了让程序员明白这是该类的一个代理模块,如果不是继承的,看起来会和适配器模式很像。  proxy类中的基类指针可以改成一个容器,管理多个对象,这在游戏服务器的网关里经常看到。 ———————————————— 原文链接:https://blog.csdn.net/zhoujiajie0521/article/details/122194799 
  • [问题求助] 签出事件未获取到
    【问题来源】【必填】南网电网【问题简要】【必填】坐席调用签出接口后单轮训事件获取不到签出成功事件,轮训状态获取接口再次调用直接返回105-005报错,没有明确的签出事件获取的情况下该如何判断坐席已经正常签出【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】坐席调用签出接口后单轮训事件获取不到签出成功事件,轮训状态获取接口再次调用直接返回105-005报错,没有明确的签出事件获取的情况下该如何判断坐席已经正常签出【日志或错误截图】【可选】【附件】【可选】无
  • [问题求助] 请求休息前提条件
    【问题来源】【必填】南网电网【问题简要】【必填】示忙状态下调用请求休息接口报错100-009【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】示忙状态下调用请求休息接口报错100-009,空闲态下可以直接状态变更。请求休息的前置条件都有什么具体的限制吗,只能在空闲态下进行变更吗,不存在之前说到的接口延迟执行的情况吗【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] transfer转接方式
    【问题来源】【必填】南网电网【问题简要】【必填】transfer转接方式devicetype选择5外呼号码,该号码可以是任意的手机号或者热线号码吗【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】transfer转接方式devicetype选择5外呼号码,该号码可以是任意的手机号或者热线号码吗【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 签入后如何判断接话权限
    【问题来源】【必填】南网电网【问题简要】【必填】拥有不接来话权限的角色签入后是默认处于不接来话的状态还是要在签入成功后调用设置是否接听来话接口进行权限控制【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】拥有不接来话权限的角色签入后是默认处于不接来话的状态还是要在签入成功后调用设置是否接听来话接口进行权限控制。【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 通话转移后,生成录音是一通还是两通,以及callid会变更吗
    【问题来源】【必填】南网电网【问题简要】【必填】通话转移后,生成录音是一通还是两通,以及callid会变更吗【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】通话转移后,生成录音是一通还是两通,以及callid会变更吗。【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 视频坐席如何进行集成
    【问题来源】【必填】南网电网【问题简要】【必填】视频坐席如何进行集成,视频电话页面该如何打开,并且视频文件获取跟录音有何区别【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】视频坐席如何进行实现,坐席签入时选择视频类型,那视频来话页面怎么加载,并且视频通话下按钮操作是否与语音情况下接口调用一致。视频来话情况下视频文件是否也跟录音一样通过录音事件标志开启,并且返回filename为视频文件路径【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] t_cms_callin_skill_5min区分数据
    【问题来源】    【南方电网】【问题简要】计算规则【问题类别】    【可选问题分类:CMS开发】【AICC解决方案版本】    【AICC可选择版本:AICC 24.200】【期望解决时间】2024年9月6日【问题描述】    查询表:     呼入技能队列维度指标5分钟粒度表【表代码:t_cms_callin_skill_5min】     区分数据条件:     统计的技能队列编号【current_skill_id】、媒体类型【media_type】区分数据,     数据结果:     以第6条和第8条数据为例 :技能队列编号相同、媒体类型相同、时间相同,指标不同,还有别的字段去区分这2条数据?还是脏数据查询SQL如下:SELECT     lin5.log_date,     log_quarter,     log_half,     log_hour,     lin5.call_type,     lin5.current_skill_id,     lin5.fail_wait_ans_time,     lin5.media_type,     lin5.local_log_week FROM     aicc_cms.t_cms_callin_skill_5min AS lin5 ORDER BY     lin5.log_date DESC截图如下:
  • [问题求助] 坐席监控权限及操作
    【问题来源】【必填】南网电网【问题简要】【必填】如何判断坐席拥有质检权限,该怎么设置权限。并且强制操作时状态如何变更【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】1.坐席对另一个坐席通话进行监听拦截插入,对坐席状态进行强制示闲示忙,需要判断坐席有没有权限进行这些操作吗,该如何进行权限赋予。2.通话态时可以进行监听拦截插入操作,强制示闲示忙签出有状态限制吗,如坐席处于空闲状态可进行强制示忙签出操作,坐席处于示忙态可以进行强制示闲签出操作,如果坐席处于休息和工作态时,是否能直接进行强制示闲示忙签出操作,还需要先调用退出休息退出工作态接口吗,直接调用强制操作接口是不是就可以实现状态直接转换的操作【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 各状态间的切换如何实现
    【问题来源】【必填】南网电网【问题简要】【必填】示忙状态到空闲状态,工作态和休息态的结束等,如何进行各状态间的跳转【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】状态间的切换如何实现,示忙态时能否通过示闲接口直接进入到示闲状态,工作态时能否同时调用空闲和示忙接口分别进入不同状态而不需要调用取消工作态接口。取消示忙、取消休息和退出工作态等接口使用场景是什么样的。比如调用取消休息接口成功后AgentState_cancelRest_Success状态返回,怎么判断取消休息后进入什么状态中,示忙还是空闲【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 录音事件该如何触发
    【问题来源】【必填】南网电网【问题简要】【必填】录音事件该如何触发【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】录音开始 AgentMediaEvent_Record 事件在restful接口情况下是否会自动触发,需手动触发开始录音接口吗【日志或错误截图】【可选】无【附件】【可选】无
总条数:764 到第
上滑加载中