设计模式 二、创建型设计模式
GoF是 “Gang of Four”(四人帮)的简称,它们是指4位著名的计算机科学家:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。他们合作编写了一本非常著名的关于设计模式的书籍《Design Patterns: Elements of Reusable Object-Oriented Software》(设计模式:可复用的面向对象软件元素)。这本书在软件开发领域具有里程碑式的地位,对面向对象设计产生了深远影响。
GoF提出了23种设计模式,将它们分为三大类:
1、创建型模式(Creational Patterns):这类模式主要关注对象的创建过程。它们分别是:
- 单例模式(Singleton)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
2、结构型模式(Structural Patterns):这类模式主要关注类和对象之间的组合。分别是:
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
3、行为型模式(Behavior Patterns):这类模式主要关注对象之间的通信。它们分别是:
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
这些设计模式为面向对象软件设计提供了一套可复用的解决方案。掌握和理解这些模式有助于提高软件开发人员的编程技巧和设计能力。
一、单例设计模式
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是单例类,这种设计模式就叫单例设计模式,简称单例模式。
1、为什么要使用单例
1.1 表示全局唯一
如果有些数据在系统种应该且有只能保存一份,那就应该设计为单例类。如:
配置类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,应该被映射为一个唯一的【配置实例】,此时就可以使用单例,当然也可以不用。
全局计数器:我们使用一个全局的计数器进行数据统计、生成全局递增ID等功能。若计数器不唯一,很有可能产生统计无效,ID重复等。
public class GlobalCounter {private AtomicLong atomicLong = new AtomicLong(0);private static final GlobalCounter instance = new GlobalCounter();// 私有化无参构造器private GlobalCounter() {}public static GlobalCounter getInstance() {return instance;}public long getId() {return atomicLong.incrementAndGet();}}// 查看当前的统计数量long courrentNumber = GlobalCounter.getInstance().getId();
以上代码也可以实现全局ID生成器的代码。
1.2 处理资源访问冲突
如果让我们设计一个日志输出的功能。如下:
public class Logger {private String basePath = "D://info.log";private FileWriter writer;public Logger() {File file = new File(basePath);try {writer = new FileWriter(file, true); //true表示追加写入} catch (IOException e) {throw new RuntimeException(e);}}public void log(String message) {try {writer.write(message);} catch (IOException e) {throw new RuntimeException(e);}}public void setBasePath(String basePath) {this.basePath = basePath;}}
我们可能会这样使用:
@RestController("user")public class UserController {public Result login(){
// 登录成功Logger logger = new Logger();logger.log("tom logged in successfully.");
// ...return new Result();}}
这样写会产生如下的问题:多个logger实例,在多个线程中,同时操作同一个文件。就可能产生相互覆盖的问题。因为tomcat处理每一个请求都会使用一个新的线程。此时日志文件就成了一个共享资源,但凡是多线程访问共享资源,我们都要考虑并发修改产生的问题。
时间处理的方法有很多,其中之一就是可以加锁:
- 如果使用单个实例输出日志,锁【this】即可。
- 如果要保证JVM级别防止日志文件访问冲突,锁【class】即可。
- 如果要保证集群服务级别的防止日志文件访问冲突,加分布式锁即可。
如果我们是一个简单工程,对日志输入要求不高。单例模式的解决思路就十分合适,既然同一个Logger无法并行输出到一个文件中,那么针对这个日志文件创建多个logger实例也就失去了意义,如果工程要求我们所有的日志输出到同一个日志文件中,这样其实并不需要创建大量的Logger实例,这样的好处有:
- 一方面节省内存空间。
- 另一方面节省系统文件句柄(对于操作系统来说,文件句柄也是一种资源,不能随便浪费)。
按照这个设计思路,实现Logger单例类。具体代码如下所示:
public class Logger {private String basePath = "D://log/";private static Logger instance = new Logger();private FileWriter writer;private Logger() {File file = new File(basePath);try {writer = new FileWriter(file, true); //true表示追加写入} catch (IOException e) {throw new RuntimeException(e);}}public static Logger getInstance(){return instance;}public void log(String message) {try {writer.write(message);} catch (IOException e) {throw new RuntimeException(e);}}public void setBasePath(String basePath) {this.basePath = basePath;}}
除此之外,并发队列(比如Java中的BlockingQueue)也可以解决这个问题:多个线程同时往并发队列里写日志,一个单独的线程负责将并发队列中的数据写入到日志文件。这种方式实现起来也稍微有点复杂。当然,我们还可将其延申至消息队列处理分布式系统的日志。
2、如何实现一个单例
常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:
1)构造器需要私有化。
2)暴露一个公共的获取单例对象的接口
3)是否支持懒加载(延迟加载)
4)是否线程安全
2.1 饿汉式
饿汉式的实现方式比较简单。在类加载的时候,在instance 静态实例就已经创建并初始化好了,所以,instance实例的创建过程是线程安全的。从名字中我们也可以看出这一点。具体的代码如下所示:
public class EagerSingleton {private static Singleton instance = new Singleton();private Singleton (){}public static Singleton getInstance() {return instance;}}
事实上,饿汉式的写法在工作上反而应该被提倡,面试中不问,只是因为它简单。很多人觉得饿汉式不能支持懒加载,即使不使用也会浪费资源,一方面是内存资源,一方面会增加初始化的开销。
1、现代计算机不缺这一个对象的内存
2、如果一个实例初始化的过程复杂那更加应该放在启动时处理,避免卡顿或者构造问题发生在运行时,满足fail-fast 的设计原则。
2.2 懒汉式
有饿汉式,对应的,就有懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载,具体的代码实现如下所示:
public class LazySingleton {private static Singleton instance;private Singleton (){}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
以上的写法本质上是有问题,当面对大量并发请求时,其实是无法保证其单例的特点的,很有可能会超过一个线程同时执行了 new Singleton();
当然解决它的方案也很简单,加锁呗:
public class Singleton {private static Singleton instance;private Singleton (){}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
以上的写法确实可以保证jvm中有且仅有一个单例实例存在,但是方法上加锁会极大的降低获取单例对象的并发度。同一时间只有一个线程可以获取单例对象,为了解决以上的方案就有第三种写法。
2.3 双重检查锁
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式:
在这种实现方式中,只要instance被创建之后,即便再调用 getInstance() 函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。具体的代码实现如下所示:
public class DclSingleton {// volatile如果不加可能会出现半初始化的对象// 现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序),为了兼容性我们加上private volatile static Singleton singleton;private Singleton (){}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
2.4 静态内部类
我们再来看一种比双重检测更加简单的实现方法,那就是利用Java的静态内部类。它们有点类似饿汉式,但又能做到了延迟加载。代码实现:
public class InnerSingleton {/** 私有化构造器 */private Singleton() {}/** 对外提供公共的访问方法 */public static Singleton getInstance() {return SingletonHolder.INSTANCE;}/** 写一个静态内部类,里面实例化外部类 */private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}}
SingletonHolder 是一个静态内部类,当外部为Singleton 被加载的时候,并不会创建SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingleHolder 才会被加载,这个时候才会创建 instance 。insance 唯一性、创建过程的线程安全性,都有jvm来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
2.5 枚举
最后介绍一种最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。具体的代码如下所示:
这是最简单的实现,因为枚举类中,每一额枚举项本身就是一个单例的:
public enum EnumSingleton {INSTANCE;}
更通用的写法如下:
public class EnumSingleton {private Singleton(){}public static enum SingletonEnum {EnumSingleton;private EnumSingleton instance = null;private SingletonEnum(){instance = new Singleton();}public EnumSingleton getInstance(){return instance;}}}
事实上我们还可以将单例项作为枚举的成员变量,我们的累加器可以这样编写:
public enum GlobalCounter {INSTANCE;private AtomicLong atomicLong = new AtomicLong(0);public long getNumber() {return atomicLong.incrementAndGet();}}
这种写法是Head-first 中推荐的写法,他除了可以和其他的方式一样实现单例,他还能有效的防止反射入侵。
2.6 反射入侵
事实上,想要阻止其他人构造实例仅仅私有化构造器还是不够的,因为我们还可以使用反射获取私有构造器进行构造,当然使用枚举的方式是可以解决这个问题的,对于其他的书写方案,我们通过下边的方式解决:
public class Singleton {private volatile static Singleton singleton;private Singleton (){if(singleton != null)throw new RuntimeException("实例:【"+ this.getClass().getName() + "】已经存在,该实例只允许实例化一次");}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
此时方法如下:
@Testpublic void testReflect() throws NoSuchMethodException,InvocationTargetException, InstantiationException, IllegalAccessException {Class<DclSingleton> clazz = DclSingleton.class;Constructor<DclSingleton> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);boolean flag = DclSingleton.getInstance() == constructor.newInstance();log.info("flag -> {}",flag);}
结果如下:
2.7 序列化与反序列化安全
事实上,到目前为止,我们的单例依然是由漏洞的,看如下代码:
@Testpublic void testSerialize() throws IllegalAccessException,NoSuchMethodException, IOException, ClassNotFoundException {// 获取单例并序列化Singleton singleton = Singleton.getInstance();FileOutputStream fout = new FileOutputStream("D://singleton.txt");ObjectOutputStream out = new ObjectOutputStream(fout);out.writeObject(singleton);// 将实例反序列化出来FileInputStream fin = new FileInputStream("D://singleton.txt");ObjectInputStream in = new ObjectInputStream(fin);Object o = in.readObject();log.info("他们是同一个实例吗?{}",o == singleton);}
我们发现,即使我们废了九牛二虎之力还是没能阻止他返回false,结果如下:
readResolve()方法可以用于替换从流中读取的对象,在进行反序列化时,会尝试执行readResolve()方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在:
public class Singleton implements Serializable {// 省略其他的内容public static Singleton getInstance() {}// 需要加这么一个方法public Object readResolve(){return singleton;}}
3、源码应用
事实上、我们在JDK或者其他的通用框架中很少能看到标准的单例设计模式,这也就意味着他确实很经典,但严格的单例设计模式确实有它的问题和局限性,我们先看看在源码中的一些案例。
3.1 jdk中的单例
jdk中有一个类是一个标准单例模式 -> Runtime类,该类封装了运行时的环境,每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。一般不能实例化一个Runtime 对象,应用程序也不能创建自己的Runtime 类实例。但可以通过getRuntime 方法获取当前Runtime 运行时对象的引用。
public class Runtime {// 典型的饿汉式private static final Runtime currentRuntime = new Runtime();private static Version version;public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}public void exit(int status) {@SuppressWarnings("removal")SecurityManager security = System.getSecurityManager();if (security != null) {security.checkExit(status);}Shutdown.exit(status);}public Process exec(String command) throws IOException {return exec(command, null, null);}public native long freeMemory();public native long maxMemory();public native void gc();}
测试用例:
@Testpublic void testRunTime() throws IOException {Runtime runtime = Runtime.getRuntime();Process exec = runtime.exec("ping 127.0.0.1");InputStream inputStream = exec.getInputStream();byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) > 0 ){System.out.println(new String(buffer,0,len, Charset.forName("GBK")));}long maxMemory = runtime.maxMemory();log.info("maxMemory-->{}", maxMemory);}
3.2 Mybatis 中的单例
Mybatis 中的org.apache.ibatis.io.VFS 使用到了单例模式。VFS就是Virtual File System的意思,mybatis 通过VFS来查找指定路径下的资源。查看VFS一级它的实现类,不难发现,VFS的角色就是对更“底层”的查找指定资源的方法的封装,将复杂的“底层”操作封装到易于使用的高层模块中,方便使用者使用。
省略了和单例无关的其他代码,并思考它使用了哪一种形式的单例:
public class public abstract class VFS {// 使用了内部类private static class VFSHolder {static final VFS INSTANCE = createVFS();@SuppressWarnings("unchecked")static VFS createVFS() {
// ...省略创建过程return vfs;}}public static VFS getInstance() {return VFSHolder.INSTANCE;}}
@Testpublic void testVfs() throws IOException {DefaultVFS defaultVFS = new DefaultVFS();
// 1、加载classpath下的文件List<String> list = defaultVFS.list("com/ydlclass");log.info("list --> {}" ,list);
// 2、加载jar包中的资源list = defaultVFS.list(new URL("file://D:/software/repository/com/mysql/mysqlconnector-j/8.0.32/mysql-connector-j-8.0.32.jar"),"com/mysql/cj/jdbc" );log.info("list --> {}" ,list);}
4、单例存在的问题
尽管单例是一个很经典的设计模式,但在实际的开发中,我们也很少按照严格的定义去使用它,以上的知识大多似乎为了理解个面试而使用和学习,有些人甚至认为单例是一种反模式(ant-pattern),压阵就不推荐使用。
大部分情况下,我们在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID生成器类。单例模式书写简洁、使用方便,在代码中,我们不需要创建对象。但是,这种使用方法有点类似硬编码(hard code),会带来诸多问题,所以我们一般会使用spring的单例容器作为替代方案。那单例究竟存在哪些问题呢?
4.1 无法支持面向对象编程
OOP的三大特性是封装、继承、多态。单例将构造私有化,直接导致的结果就是无法成为其他类的父类,这就相当于直接放弃了继承和多态的特性,相当于损失了可以应对未来需求变化的扩展性,以后一旦有扩展需求,比如写一个类似的具有绝大部分相同功能的单例,我们不得不新建一个雷同的单例。
4.2 极难的横向扩展
单例类只能有一个对象实例。如果未来某一天,一个实例无法满足现在的需求,当需要创建多个实例时,就必须对源代码进行修改,无法友好的扩展。
例如,在系统设计初期,我们觉得应该有一个数据库连接池,这样能方便我们控制对数据库连接资源的消耗,所以我们把数据库连接池类设置成了单例类。但之后我们发现,系统中有些sql运行得非常慢。这些sql语句在执行得时候,长时间占用数据库连接池连接资源,导致其他sql请求无法响应。为了解决这个问题,我们希望将慢sql与其他sql隔离开来执行。为了实现这样的目的,我们可以在系统中创建两个数据库连接池,慢sql独享一个数据库连接池,其他sql独享另外一个数据库连接池,这样就能避免慢sql影响到其他sql的执行。
如果我们将数据库连接池设计成单例类,显然就无法适应这样的需求变更,也就是说,单例类在某些情况下会影响代码的扩展性、灵活性。所以,数据库连接池、线程池这类的资源池,最好还是不要设计成单例类。实际上,一些开源的数据库连接池、线程池也确实没有设计成单例类。
5、不同作用范围的单例
首先看一下单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式”。
定义中提到,“一个类只允许创建唯一 一个对象”。那对象的唯一性的作用范围是什么呢?在标准的单例设计模式中,其单例是进程唯一的,也就意味着一个项目启动,在其整个运行环境中只能有一个实例。
事实上,在实际的工作当中,我们能够看到极多(只有一个实例的情况),但是大多并不是标准的单例设计模式,如:
1)使用ThreadLocal 实现的线程级别的单一实例
2)使用spring实现的容器级别的单一是实例。
3)使用分布式锁实现的集群状态的唯一实例。
以上的情况都不是标准的单例设计模式,但我们可以将其看做单例设计模式的扩展,我们以前两种情况为例进行介绍。
5.1 线程级别的单例
刚才说了单例类对象是进程唯一的,一个进程只能有一个单例对象。如何实现一个线程唯一的单例呢?
如果在不允许使用ThreadLocal 的时候我们可能想到如下的解决方案,定义一个全局的线程安全的ConcurrentHashMap,以线程id为key,以实例为value,每个线程的存取都从共享的map中进行操作,代码如下:
public class Connection {private static final ConcurrentHashMap<Long, Connection> instances= new ConcurrentHashMap<>();private Connection() {}public static Connection getInstance() {Long currentThreadId = Thread.currentThread().getId();instances.putIfAbsent(currentThreadId, new Connection());return instances.get(currentThreadId);}}
事实上ThreadLocal的原理也大致如此:
项目中的ThreadLocal的使用场景:
1)在spring使用ThreadLcoal对当前线程和一个连接资源进行绑定,实现事务管理:
public abstract class TransactionSynchronizationManager {// 本地线程中保存了当前的连接资源,key(datasource)--> value(connection)private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");// 保存了当前线程的事务同步器private static final ThreadLocal<Set<TransactionSynchronization>>synchronizations = new NamedThreadLocal<>("Transaction synchronizations");// 保存了当前线程的事务名称private static final ThreadLocal<String> currentTransactionName =new NamedThreadLocal<>("Current transaction name");// 保存了当前线程的事务是否只读private static final ThreadLocal<Boolean> currentTransactionReadOnly =new NamedThreadLocal<>("Current transaction read-only status");// 保存了当前线程的事务隔离级别private static final ThreadLocal<Integer> currentTransactionIsolationLevel =new NamedThreadLocal<>("Current transaction isolation level");// 保存了当前线程的事务的活跃状态private static final ThreadLocal<Boolean> actualTransactionActive =new NamedThreadLocal<>("Actual transaction active");}
2)在spring中使用RequestContextHolder ,可以再一个线程中轻松的获取request、response和session。如果将我们在静态方法,切面中想获取一个request 对象就可以使用这个类。
public abstract class RequestContextHolder {private static final ThreadLocal<RequestAttributes> requestAttributesHolder =new NamedThreadLocal("Request attributes");private static final ThreadLocal<RequestAttributes>inheritableRequestAttributesHolder = newNamedInheritableThreadLocal("Request context");@Nullablepublic static RequestAttributes getRequestAttributes() {RequestAttributes attributes =(RequestAttributes)requestAttributesHolder.get();if (attributes == null) {attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();}return attributes;}}
ServletRequestAttributes:
public class ServletRequestAttributes extends AbstractRequestAttributes {public static final String DESTRUCTION_CALLBACK_NAME_PREFIX =ServletRequestAttributes.class.getName() + ".DESTRUCTION_CALLBACK.";protected static final Set<Class<?>> immutableValueTypes = new HashSet(16);private final HttpServletRequest request;@Nullableprivate HttpServletResponse response;@Nullableprivate volatile HttpSession session;private final Map<String, Object> sessionAttributesToUpdate;}
public abstract class PageMethod {protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();protected static boolean DEFAULT_COUNT = true;}
5.2 容器范围的单例
有时候我们将单例的作用范围由进程切换到一个容器,可能会更加方便进行单例对象的管理。这也是spring作为java生态大哥大核心思想。spring通过提供一个单例容器,来确保一个实例在容器级别单例,并且可以在容器启动时完成初始化,它的优势如下:
1)所有的bean 以单例形式存在于容器中,避免大量的对象被创建,造成jvm内存抖动严重,频繁gc
2)程序启动时,初始化单例bean,满足fast-fail,将所有构建过程的异常暴露在启动时,而非运行时。更加安全。
3)缓存了所有单例bean,启动的过程相当于预热的过程,运行时不必进行对象创建,效率更高。
4)容器管理bean的生命周期,结合依赖注入使得解耦更加彻底、扩展性无敌。
5.3 日志中的多例
在日志框架中,我们可以通过LoggerFactory.getLogger("ydl")方法获取一个实例:
@Testpublic void testLogger(){Logger ydl = LoggerFactory.getLogger("ydl");Logger ydl2 = LoggerFactory.getLogger("ydl");Logger ydlclass = LoggerFactory.getLogger("ydlclass");log.info("ydl == ydl2 -->{}", ydl == ydl2);log.info("ydl == ydlclass --> {}", ydl == ydlclass);}
其结果如下:
我们发现,如果我们使用相同的名字,它会返回同一个实例,否则就是另一个实例,这其实就是一个多例,一个类可以创建多个对象,但是个数是有限制的,他可是是具体约定好的个数,比如5,也可以按照类型的个数创建。
这种多例模式有点类似工厂模式。它跟工厂模式的不同之处是,多例模式创建的对象都是同一个类的对象,而工厂模式创建的是不同子类的对象。实际上,它还有点类似享元模式,两者的区别等到我们讲到享元模式的时候再来分析。除此之外,实际上,枚举类型也相当于多例模式,一个类型只能对应一个对象,一个类可以创建多个对象。
二、工厂设计模式
一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。
在GoF的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见。
在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。
1、如何实现工厂模式
1)简单工厂(Simple Factory)
简单工厂叫做静态工厂方法模式(Static Factory Method Pattern).学习此设计模式时,我们会从一个案例不断优化带着大家领略工厂设计模式的魅力。
现在有一个场景,我们需要一个资源加载器,他要根据不同的url进行资源加载,但是如果我们将所有的加载实现代码全部封装在了一个load方法中,就会导致一个类很大,同时扩展性也很查,但想要添加新的前缀解析其他类型的url时,发现需要修改大量的源代码,我们的代码如下:
定义两个需要之后会用到的类,非常简单:
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Resource {private String url;
}
public class ResourceLoadException extends RuntimeException{public ResourceLoadException() {super("加载资源是发生问题。");}public ResourceLoadException(String message) {super(message);}
}
源码如下:
public class ResourceLoader {public Resource load(String filePath) {String prefix = getResourcePrefix(filePath);Resource resource = null;if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂return new Resource(url);} else if ("file".equals(type)) {
// ..建立流,做异常处理等等return new Resource(url);} else if ("classpath".equals(type)) {
// ...return new Resource(url);} else {return new Resource("default");}return resource;}private String getPrefix(String url) {if(url == null || "".equals(url) || !url.contains(":")){throw new ResourceLoadException("此资源url不合法.");}String[] split = url.split(":");return split[0];}
}
上边的案例,存在很多的if分支,如果分支数量不多,且不需要扩展,这压根的编写方式当然没错,然而在实际的工作场景中,我们的业务代码可能会很多,分支逻辑也可能十分复杂,这个时候简单工厂设计模式就要发挥作用了。
我们只需要创建一个工厂类,将创建资源的能力交给工厂即可:
public class ResourceFactory {public static Resource create(String type,String url){if("http".equals(type)){
// ..发起请求下载资源... 可能很复杂return new Resource(url);} else if ("file".equals(type)) {
// ..建立流,做异常处理等等return new Resource(url);} else if ("classpath".equals(type)) {
// ...return new Resource(url);} else {return new Resource("default");}}
}
有了上面的工厂类之后,我们的住哟啊逻辑就会简化:
public class ResourceLoader {public Resource load(String url){
// 1、根据url获取前缀String prefix = getPrefix(url);
// 2、根据前缀处理不同的资源return ResourceFactory.create(prefix,url);}private String getPrefix(String url) {if(url == null || "".equals(url) || !url.contains(":")){throw new ResourceLoadException("此资源url不合法.");}String[] split = url.split(":");return split[0];}
}
这就是简单工厂设计模式,提取一个工厂类,工厂会根据传入的不同的类型,创建不同的产品。好处如下:
将创建对象的过程交给工厂类、其他业务需要某个产品时,直接使用create(方法名不重要)创建即可这样的好处时:
1)工厂将创建的过程进行封装,不需要关系创建的细节,更加符合面向对象思想
2)这样主要业务逻辑不会被创建对象的代码干扰,代码更易阅读
3)产品的创建可以独立测试,更将容易测试
4)独立的工厂类只负责创建产品,更加符合单一原则
绝大部分工厂类都是以“Factory”单词结尾,但也不是必须的,比如 Java 中的DateFormat、Calender。除此之外,工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等,这个我们根据具体的场景和习惯来命名就好。
2)工厂方法(Factory Method)
如果有一天,我们if分支逻辑不断膨胀,有变为肿瘤代码的可能,就有必要将if分支逻辑去掉,那又该怎么办呢?比较经典的处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。我们会为每一个Resource 创建一个独立的工厂类,形成一个个小作坊,将每一个实例的创建过程交给工厂类完成,重构之后的代码如下所示:
我们将生产资源的工厂类进行抽象:
public interface IResourceLoader {Resource load(String url);
}
并为每一种资源创建与之匹配的实现:
public class ClassPathResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {
// 中间省略复杂的创建过程return new Resource(url);}
}
public class FileResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {
// 中间省略复杂的创建过程return new Resource(url);}
}
public class HttpResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {
// 中间省略复杂的创建过程return new Resource(url);}
}
public class FtpResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {
// 中间省略复杂的创建过程return new Resource(url);}
}
public class DefaultResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {
// 中间省略复杂的创建过程return new Resource(url);}
}
这就是共工厂方法模式的典型代码实现。这样当我们新增一种读取资源的方式时,只需要新增一个实现,并实现IResourceLoader 接口即可。所以,工厂方式模式比起简单工厂模式更加符合开闭原则。
也就是说,我们有很多资源需要加载,可能是各种各样的加载方式,比如http,比如file文件,因为加载的方式不同,所以我们要写不同的方法来加载,工厂模式的意思就是说,我们先出一个接口,里面有一个方法就是加载资源的方法 load ,然后你加载的资源不一样,就根据这个接口实现不同的加载方法,也就是将每个方法隔离开。如果后面要加载不同的资源,就实现接口,重新写不同的加载方法。
上面的方法实现完之后,我们就可以将逻辑改成直接调用对应的工厂实现类了:
public class ResourceLoader {public Resource load(String url){// 1、根据url获取前缀String prefix = getPrefix(url);ResourceLoader resourceLoader = null;// 2、根据前缀选择不同的工厂,生产独自的产品// 版本一if("http".equals(prefix)){resourceLoader = new HttpResourceLoader();} else if ("file".equals(prefix)) {resourceLoader = new FileResourceLoader();} else if ("classpath".equals(prefix)) {resourceLoader = new ClassPathResourceLoader()} else {resourceLoader = new DefaultResourceLoader();}return resourceLoader.load(url);}private String getResourcePrefix(String filePath) {if (filePath == null || "".equals(filePath)) {throw new RuntimeException("The file path is illegal");}filePath = filePath.trim().toLowerCase();String[] split = filePath.split(":");if (split.length > 1) {return split[0];} else {return "classpath";}}
}
其实上面的代码还比较复杂,我们可以将工厂方法的实现类加入到缓存中,简化调用逻辑:
例如一:将实现类放入map集合:
private static Map<String,IResourceLoader> resourceLoaderCache = newHashMap<>(8);
// 版本二static {resourceLoaderCache.put("http",new HttpResourceLoader());resourceLoaderCache.put("file",new FileResourceLoader());resourceLoaderCache.put("classpath",new ClassPathResourceLoader());resourceLoaderCache.put("default",new DefaultResourceLoader());}
然后调用逻辑变成如下所示:根据map集合调用,获取对应实现类。
public Resource load(String url){// 1、根据url获取前缀String prefix = getPrefix(url);return resourceLoaderCache.get(prefix).load(url);}
当然也可以将这些实现类放入在配置类中,在配置类中进行加载:
http=com.ydlclass.factoryMethod.resourceFactory.impl.HttpResourceLoader
file=com.ydlclass.factoryMethod.resourceFactory.impl.FileResourceLoader
classpath=com.ydlclass.factoryMethod.resourceFactory.impl.ClassPathResourceLoader
default=com.ydlclass.factoryMethod.resourceFactory.impl.DefaultResourceLoader
这样就可以在static中这样编码:通过加载文件,来加载对应实现类,然后如果有新的实现类,修改对应文件即可。
static {InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("resourceLoader.properties");Properties properties = new Properties();try {properties.load(inputStream);for (Map.Entry<Object,Object> entry : properties.entrySet()){String key = entry.getKey().toString();Class<?> clazz = Class.forName(entry.getValue().toString());IResourceLoader loader = (IResourceLoader)clazz.getConstructor().newInstance();resourceLoaderCache.put(key,loader);}} catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {throw new RuntimeException(e);}}
上面工厂方法的实现逻辑,实际上我们工厂方法一般会生成一个对应的产品,我们一般会将整个产品线也进行抽象:
public abstract class AbstractResource {private String url;public AbstractResource(){}public AbstractResource(String url) {this.url = url;}protected void shared(){System.out.println("这是共享方法");}/*** 每个子类需要独自实现的方法* @return 字节流*/public abstract InputStream getInputStream();
}
具体的产品需要继承这个抽象类,实现自己的产品逻辑:
public class ClasspathResource extends AbstractResource {public ClasspathResource() {}public ClasspathResource(String url) {super(url);}@Overridepublic InputStream getInputStream() {return null;}
}
然后我们修改对应的工厂方法,变成在工厂中生成对应的产品:
public class ClassPathResourceLoader implements IResourceLoader {@Overridepublic AbstractResource load(String url) {// 中间省略复杂的创建过程return new ClasspathResource(url);}
}
这样调用方法就变成了:
@Testpublic void testFactoryMethod(){String url = "file://D://a.txt";ResourceLoader resourceLoader = new ResourceLoader();AbstractResource resource = resourceLoader.load(url);log.info("resource --> {}",resource.getClass().getName());}
梳理一下逻辑:
首先有两个部分,一个工厂方法,一个产品方法,这样个方法都有自己的抽象类也就是接口,其中工厂方法中会调用对应的产品方法,但是工厂方法的返回值是产品的抽象类,这就意味着工厂方法没有直接跟对应的产品方法实例进行依赖,而是依赖的产品方法的抽象类。每增加一个产品,一个工厂,只需要实现各自对应的接口即可。
3)抽象工厂(Abstract Factory)
相关文章:
设计模式 二、创建型设计模式
GoF是 “Gang of Four”(四人帮)的简称,它们是指4位著名的计算机科学家:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。他们合作编写了一本非常著名的关于设计模式的书籍《Design Patterns: Elements of Reusable…...
51c大模型~合集73
我自己的原文哦~ https://blog.51cto.com/whaosoft/12318419 #Emu3 视频、图像、文本,只需基于下一个Token预测:智源Emu3发布,验证多模态模型新范式 OpenAI 前首席科学家、联合创始人 Ilya Sutskever 曾在多个场合表达观点࿱…...
【el-upload】el-upload组件 - list-type=“picture“ 时,文件预览展示优化
目录 问题图el-upload预览组件 PicturePreview效果展示 问题图 el-upload <el-uploadref"upload"multipledragaction"#":auto-upload"false":file-list"fileList"name"files":accept".png,.jpg,.jpeg,.JGP,.JPEG,.…...
STM32F103系列配置中断向量表偏移(Keil/STM32CubeIDE)
需要在flash中添加bootloader的话,需要对flash进行分区,即bootloader区和app区(程序运行区),主要记录在 Keil 平台和 STM32CubeIDE平台 上的中断向量表偏移配置,以偏移 0x2800 为例,即预留10k大小的空间给bootloader …...
Redis常用数据类型和使用常见以及基本操作举例(适合初学者,以医药连锁管理系统为背景)
Redis的常见数据类型,包括String、Hash、List、Set、Zset等,这些数据类型都有各自的特点和适用场景。接下来,将这些数据类型与医药连锁管理系统的业务场景进行匹配。 String类型,适合存储单个值。在医药连锁管理系统中࿰…...
ASL扩展坞方案|Type-c转换器方案|ASL原厂代理商
安格瑞科技代理的ASL主板组件系列包括CS5211、CS5311、CS5232、CS5263、CS621x、CS5523、CS5518等产品; CS5228ANDP to HDMI(4K60HZ)CS5262ANDP (4lanes) to HDMI2.0 4k60Hz VGACS5263ANDP(4lanes) to HDMI2.0 4k60HzCS5363ANDP (4lanes) to HDMI2.0 4k60Hz CS521…...
论文略读(2025.3.18-更新中)
关于可控视频生成 I2V3D: Controllable image-to-video generation with 3D guidance Image to Video工作,能够实现给一张图,输出一个视频,且可以控制相机。动态信息来自于用户手工设计(相机移动,人体骨骼驱动&#x…...
基于SpringBoot的“校园招聘网站”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“校园招聘网站”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…...
【Linux进程七】程序地址空间
【Linux进程七】程序地址空间 1.进程的地址空间分布2.类型的本质是偏移量3.什么是进程地址空间4.页表的映射和访问权限字段5.地址空间的作用 1.进程的地址空间分布 堆是向上扩展的,栈是向下扩展的 因为字符常量区和代码区相邻,受到同样的保护,…...
Linux C/C++编程——线程
线程是允许应用程序并发执行多个任务的一种机制,线程参与系统调度。 系统调度的最小单元是线程、而并非进程。 线程包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流)…...
【Spring Boot 中 `@Value` 注解的使用】
文章目录 一、前言二、Value 注解简介三、Value 注解的常见用法1. 读取 application.properties 或 application.yml 配置值(1)配置文件示例(2)Java 代码示例(3)测试输出 2. 使用 Value 设置默认值3. 读取系…...
【CAD二次开发】调试无法进入断点提示无可用源问题(非空心断点)
问题截图:显示无可用源,关闭后F5走完后,启动的调试就中断了 操作是:打开Cad,打开dwg后,执行命令,就出现以上截图问题。 问题来源:通常是由于 AutoCAD 的 纤程模式(Fiber&…...
Ubuntu下Docker部署Misskey:打造你的去中心化社交平台
引言 在信息爆炸的时代,人们对于社交平台的需求日益增长,同时也更加注重数据的隐私和自由。Misskey作为一个开源的去中心化社交平台,为用户提供了一个全新的选择。本文将详细介绍如何在Ubuntu Linux环境下,利用Docker快速部署Mis…...
【Vue3】01-vue3的基础 + ref reactive
首先确保已经有了ES6的基础 本文介绍 vue 的基础使用以及 两种响应数据的方式。 目录 1. 创建一个vue应用程序 2. Vue模块化开发 3. ref 和 reactive 的区别 1. 创建一个vue应用程序 所需的两个文件: https://unpkg.com/vue3/dist/vue.global.js https://un…...
C++实现rabbitmq生产者消费者
RabbitMQ是一个开源的消息队列系统,它实现了高级消息队列协议(AMQP), 特点 可靠性:通过持久化、镜像队列等机制保证消息不丢失,确保消息可靠传递。灵活的路由:提供多种路由方式,如…...
Simple-BEV的bilinear_sample 作为view_transformer的解析,核心是3D-2D关联点生成
文件路径models/view_transformers 父类 是class BiLinearSample(nn.Module)基于https://github.com/aharley/simple_bev。 函数解析 函数bev_coord_to_feature_coord的功能 将鸟瞰图3D坐标通过多相机(针孔/鱼眼)内外参投影到图像特征平面࿰…...
Rust嵌入式开发环境搭建指南(基于Stm32+Vscode)
Rust嵌入式开发环境搭建指南(基于Stm32+Vscode) 部分目录如下所示: 目录 简介Rust开发环境安装STM32开发工具链安装VSCode环境配置VSCode插件安装调试器配置项目创建与配置常见问题与解决方案简介 本文档旨在指导开发者如何搭建基于Rust语言的STM32嵌入式开发环境。相比传…...
springboot操作redis集群,注意事项
整合redis可查看博文 springboot 整合redis_springboot整合redis csdn-CSDN博客 集群中操作注意事项 1 多键操作失败: 当使用multiGet等需要同时访问多个键的方法时,如果没有使用Hash Tags,这些键可能会被分配到不同的槽中。如果这些槽位于…...
计算机技术系列博客——目录页(持续更新)
1.1 博客目录专栏 1.1.1 博客文章导航 计算机技术系列博客——目录页 1.1.2 网页资源整理 2.1 计算机科学理论 2.2 软件工程技术 2.2.1.1 编程语言 Java Java语言基础 (1) Java基础知识总结01——Java基础篇 (2) Java基础知识总结02——集合框架篇 (3) Java基础知识总结03—…...
@maptalks/gl-layers中的VectorTileLayer的setStyle属性的全部line配置
maptalks/gl-layers中的VectorTileLayer的setStyle属性的全部line配置 关于 maptalks/gl-layers 中 VectorTileLayer 的 setStyle 方法 在 maptalks/gl-layers 库中,VectorTileLayer 提供了一个灵活的方式来设置矢量瓦片图层的样式。通过调用 setStyle 方法…...
sql小记,20250319
ps:基于sqlserver 一、绩效管理系统表设计 1.表设计 Users用户表:包含id,用户名,密码。 AppraisalBases评价(职位基数)表:包含职位id,职位年终奖基数 AppraisalCoeffcients评价系数表:包含类别id, 类别&…...
【亚马逊云科技】大模型选型实战(挑选和测评对比最适合业务的大模型)
文章目录 前言1、实验内容2、手册内容 一、环境准备二、Prompt 实战与模型配置2.1 基于 Amazon Bedrock 对比测试不同模型的逻辑推理效果2.2 基于 Amazon Bedrock 对比测试不同模型知识问答能力2.3 Prompt 实战结果分析 三、基于 Amazon Bedrock Evaluations 进行模型评测与自动…...
Linux 用户与组管理实战:经验分享与最佳实践
在 Linux 系统管理中,用户和组的管理是保障系统安全和资源分配的重要环节。本文将深入介绍如何创建和管理用户与组,包括 UID、GID 的设置,主组与附加组的分配,以及常见问题的排查和解决。本文还结合实际操作经验,总结了…...
详解如何通过Python的BeautifulSoup爬虫+NLP标签提取+Dijkstra规划路径和KMeans聚类分析帮助用户规划旅行路线
系统模块: 数据采集模块(爬虫):负责从目标网站抓取地点数据(如名称、经纬度、描述等) 数据预处理模块(标签算法):对抓取到的地点数据进行清洗和分类。根据地点特征&…...
Java Stream两种list判断字符串是否存在方案
这里写自定义目录标题 背景初始化方法一、filter过滤方法二、anyMatch匹配 背景 在项目开发中,经常遇到筛选list中是否包含某个子字符串,有多种方式,本篇主要介绍stream流的filter和anyMatch两种方案,记录下来,方便备…...
C语言-指针变量和变量指针
指针 预备知识 内存地址 字节:字节是内存的容量单位,英文名Byte,1Byte8bits 地址:系统为了便于区分每一个字节面对它们的逐一进行编号(编号是唯一的),称为内存地址,简称地址。int…...
CMS漏洞-WordPress篇
一.姿势一:后台修改模板拿WebShell 1.使用以下命令开启docker cd /www/wwwroot / vulhub / wordpress / pwnscriptum docker - compose up - d 如果发现不能开启,可以检查版本和端口 2.访问网址登录成功后 外观 👉编辑 👉404.…...
初识Brainstorm(matlab)
Brainstorm是一款开源应用程序,专门用于分析脑部记录数据:MEG、EEG、fNIRS、ECoG、深部电极等。该应用程序免费,而且不需要Matlab许可证。Brainstorm主要优势是简单直观的图形界面,不需要任何编程知识。具体内容,可查看…...
2025年智能系统、自动化与控制国际学术会议(ISAC 2025)
重要信息 2025 International Conference on Intelligent Systems, Automation and Control 2025年3月28-30日 | 中国西安理工大学 | 会议官网: www.icisac.org 简介 在国家大力推动高质量发展与创新驱动战略的背景下,智能制造与自动化控制行业正迎…...
GGUF、Transformer、AWQ 详解与关系梳理
GGUF、Transformer、AWQ 详解与关系梳理 一、核心概念解析 Transformer 定义 :2017 年 Google 提出的基于自注意力机制的神经网络架构,是大语言模型的通用基础架构。功能 :用于文本生成、翻译、问答等任务,如 BERT、GPT 系列、…...
学习笔记|arduino uno r3|DS1307时钟芯片|Atmega328P| 设置时间|读取时间|无源晶振:DS1307时钟芯片实验
目录 芯片pinout: 实验器件: 实验连线 解决AVR 架构不支持 printf() 方法 使用GetTimeAndDate.ino设置时间: 使用SetTimeAndDate.ino设置时间: 芯片pinout: DS1307 是美国 DALLAS 公司推出的 I 总线接口实时时钟芯…...
Linux--进程创建
进程创建 写时拷贝(时间换空间) 更新页表项权限为只读----子进程写入----触发系统错误系统缺页中断,系统开始检测,系统判断写入区域是数据区还是代码区,如果是代码区就终结进程,如果是数据区就进行写时拷贝…...
MySQL 创建用户,建库,建表
以下是在 MySQL 中创建用户、数据库、表的详细操作步骤: 一、登录 MySQL -- 使用 root 用户登录(需替换为实际密码) mysql -u root -p输入密码后回车,进入 MySQL 命令行界面。 二、创建数据库 -- 创建名为 test_db 的数据库&a…...
成都国际数字影像产业园,文创产业运营新典范深度解析
成都国际数字影像产业园位于成都市蓉北商圈金牛片区福堤路99号,是金牛区政府与树莓集团携手打造的省级“文化科技”融合示范园区。该产业园已成为西南地区乃至全国数字影像产业的一颗璀璨明珠,其成功运营模式堪称文创产业运营的新典范。 产业定位与资源…...
33、如果 std::vector 的元素是指针,需要注意什么?
对 std::vector 元素为指针的情况,需要注意以下几点: 内存管理: 如果 std::vector 存储的是原始指针,那么仅仅清空 vector 或者让 vector 被销毁,并不会释放指针所指向的内存。因此,需要确保在 vector 被销…...
Docker 速通(总结)
Docker 命令 镜像 docker build: 从 Dockerfile 构建镜像。docker pull: 从 Docker Hub 或其他注册表拉取镜像。docker push: 将镜像推送到 Docker Hub 或其他注册表。docker images: 列出本地镜像。docker rmi: 删除本地镜像。 容器 docker run: 创建并启动一个新的容器。…...
算法训练篇06--力扣611.有效三角形的个数
目录 1.题目链接:611.有效三角形的个数 2.题目描述: 3.解法一:(暴力解法)(会超时): 4.解法二(排序双指针) 1.题目链接:611.有效三角形的个数 2.题目描述: 给定一个包含非负整数的数组 nums …...
Gin框架学习
一.介绍 Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin。 下载 go get -u github.com/gin-gonic/gin 二.Gin示例 学习的时候,写在…...
青少年编程与数学 02-011 MySQL数据库应用 07课题、表的操作
青少年编程与数学 02-011 MySQL数据库应用 07课题、表的操作 一、数据库表(Table)二、创建表语法格式示例注意事项 三、字段的命名规则基本规则命名规范建议示例 四、字段数据类型数值类型字符串类型日期和时间类型其他类型 五、选择合适的数据类型1. **…...
【详细解决】pycharm 终端出现报错:“Failed : 无法将“Failed”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
昨天在终端一顿操作后突然打开pycharm时就开始报错: 无法将“Failed”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 所在位置 行:1 字符: 1 Failed to act…...
AcWing 839:模拟堆 ← multiset + unordered_map
【题目来源】 https://www.acwing.com/problem/content/841/ 【题目描述】 维护一个集合,初始时集合为空,支持如下几种操作: 1. I x,插入一个数 x; 2. PM,输出当前集合中的最小值; 3. DM&#…...
cmake教程
CMake 是一个跨平台的自动化构建系统,广泛用于管理软件构建过程。它使用 CMakeLists.txt 文件来配置项目的构建过程,并生成适用于不同编译器和操作系统的构建文件(如 Makefile、Visual Studio 项目文件等)。以下是一个简单的 CMak…...
小蓝的括号串1(栈,蓝桥云课)
问题描述 小蓝有一个长度为 nn 的括号串,括号串仅由字符 ( 、 ) 构成,请你帮他判断一下该括号串是否合法,合法请输出 Yes ,反之输出 No 。 合法括号序列: 空串是合法括号序列。 若 ss 是合法括号序列,则 (…...
软考系统架构设计师考试学习和考试的知识点大纲,覆盖所有考试考点
以下是软考系统架构设计师考试的知识点大纲,覆盖所有官方考点,分为基础知识、核心技术、系统设计、案例分析、论文写作五大模块,帮助系统性学习和备考: 一、基础知识模块 计算机组成与体系结构 计算机硬件组成(CPU、内…...
车载以太网网络测试-18【传输层-DOIP协议-1】
目录 1 摘要2 DOIP协议的概述2.1 DOIP协议背景2.2 ISO 13400概述 3 DOIP报文的帧结构以及实例3.1 DOIP报文帧结构3.2 实例示例 总结 1 摘要 在汽车网络通信中,诊断扮演了非常重要的角色,无论是故障诊断、整车下线配置,还是ECU的软件更新、远…...
密码学(Public-Key Cryptography and Discrete Logarithms)
Public-Key Cryptography and Discrete Logarithms Discrete Logarithm 核心概念:离散对数是密码学中一个重要的数学问题,特别是在有限域和循环群中。它基于指数运算在某些群中是单向函数这一特性。也就是说,给定一个群 G G G和一个生成元 …...
自然语言处理|深入解析 PEGASUS:从原理到实践
一、引言 在信息爆炸的时代,互联网上的文本数据以极快的速度增长。无论是新闻资讯、学术论文、社交媒体动态,还是各类报告文档,我们每天接触到的文字信息量巨大。如何快速、准确地提取关键内容成为一项重要任务。文本摘要技术通过将长篇文本…...
矩阵指数的定义和基本性质
1. 矩阵指数的定义 矩阵指数 e A t e^{\boldsymbol{A}t} eAt 定义为幂级数的形式: e A t ∑ k 0 ∞ ( A t ) k k ! e^{\boldsymbol{A}t} \sum_{k0}^\infty \frac{(\boldsymbol{A}t)^k}{k!} eAtk0∑∞k!(At)k 当 A \boldsymbol{A} A 为 n n n \times n …...
react 技术栈请问该如何优化 DOM 大小
针对 React 应用中 DOM 大小过大 的问题,以下是详细的优化方案和具体操作步骤,帮助你提升 Lighthouse 性能评分和用户体验: 一、问题根源分析 DOM 大小过大(如超过 1500 个节点或深度超过 32 层)会导致: …...
redis,tar.gz安装后,接入systemctl报错解决
1. WARNING Memory overcommit must be enabled! 这种报错,有两种解决方法 1.1 修改系统参数 编辑 /etc/sysctl.conf 文件,设置 overcommit_memory 为 1 vm.overcommit_memory 11.2 修改redis的最大使用内存 修改配置文件 redis.conf maxmemory 1g…...