网站建设与制作好学吗,苏州创建公司,怎么恢复网站数据库,app外包推广经典的设计模式有23种#xff0c;但是常用的设计模式一般情况下不会到一半#xff0c;我们就针对一些常用的设计模式进行一些详细的讲解和分析#xff0c;方便大家更加容易理解和使用设计模式。
1-为什么要使用单例 单例设计模式#xff08;Singleton Design Pattern… 经典的设计模式有23种但是常用的设计模式一般情况下不会到一半我们就针对一些常用的设计模式进行一些详细的讲解和分析方便大家更加容易理解和使用设计模式。
1-为什么要使用单例 单例设计模式Singleton Design Pattern理解起来非常简单。一个类只允许创建一个对象或者实例那这个类就是一个单例类这种设计模式就叫作单例设计模式简称单例模式。 从业务概念上有些数据在系统中只应该保存一份就比较适合设计为单例类。比如系统的配置信息类。除此之外我们还可以使用单例解决资源访问冲突的问题。在实际项目开发过程中比如我们在单体项目中【这里先考虑单体】中我们需要一个类专门生成id如果有多个实例会产生重复的id这种情况是我们不想看到的也是不能发生的这样的情况我们必须采用单例模式。
2-如何实现一个单例 实现单例的核心要点 1-构造函数需要是private访问权限的这样才能避免外部通过new创建实例 2-考虑对象创建时的线程安全问题 3-考虑是否支持延迟加载 4-考虑getInstance()性能是否高是否加锁
2.1-饿汉式 饿汉式的实现方式比较简单。在类加载的时候instance静态实例就已经创建并初始化好了所以instance实例的创建过程是线程安全的。不过这样的实现方式不支持延迟加载在真正用到IdGenerator的时候再创建实例。具体的代码实现如下所示
public class IdGenerator {private AtomicLong id new AtomicLong(0);private static final IdGenerator instance new IdGenerator();private IdGenerator() {}public static IdGenerator getInstance() {return instance;}public long getId() {return id.incrementAndGet();}
}
观点讨论 观点1有人觉得这种实现方式不好因为不支持延迟加载如果实例占用资源多比如占用内存多或初始化耗时长比如需要加载各种配置文件提前初始化实例是一种浪费资源的行为。最好的方法应该在用到的时候再去初始化。 观点2如果初始化耗时长那我们最好不要等到真正要用它的时候才去执行这个耗时长的初始化过程这会影响到系统的性能比如在响应客户端接口请求的时候做这个初始化操作会导致此请求的响应时间变长甚至超时。采用饿汉式实现方式将耗时的初始化操作提前到程序启动的时候完成这样就能避免在程序运行的时候再去初始化导致的性能问题。如果实例占用资源多按照fail-fast的设计原则有问题及早暴露那我们也希望在程序启动时就将这个实例初始化好。如果资源不够就会在程序启动的时候触发报错比如Java中的 PermGen Space OOM我们可以立即去修复。这样也能避免在程序运行一段时间后突然因为初始化这个实例占用资源过多导致系统崩溃影响系统的可用性。 2.2-懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
public class IdGenerator {private AtomicLong id new AtomicLong(0);private static IdGenerator instance;private IdGenerator() {}public static synchronized IdGenerator getInstance() {if (instance null) {instance new IdGenerator();}return instance;}public long getId() {return id.incrementAndGet();}
} 分析我们给getInstance()这个方法加了一把大锁synchronzed导致这个函数的并发度很低。量化一下的话并发度是1也就相当于串行操作了。而这个函数是在单例使用期间一直会被调用。如果这个单例类偶尔会被用到那这种实现方式还可以接受。但是如果频繁地用到那频繁加锁、释放锁及并发度低等问题会导致性能瓶颈这种实现方式就不可取了。 2.3-双重检测 饿汉式不支持延迟加载懒汉式有性能问题不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式也就是双重检测实现方式。在这种实现方式中只要instance被创建之后即便再调用getInstance()函数也不会再进入到加锁逻辑中了。所以这种实现方式解决了懒汉式并发度低的问题。 注意添加volatile关键字保证instance new IdGenerator(); 完全执行完成。
public class IdGenerator {private AtomicLong id new AtomicLong(0);private static volatile IdGenerator instance;private IdGenerator() {}public static IdGenerator getInstance() {if (instance null) {synchronized(IdGenerator.class) { // 此处为类级别的锁if (instance null) {instance new IdGenerator();}}}return instance;}public long getId() {return id.incrementAndGet();}
} 2.4-静态内部类 我们再来看一种比双重检测更加简单的实现方法那就是利用Java的静态内部类。它有点类似饿汉式但又能做到了延迟加载。
public class IdGenerator {private AtomicLong id new AtomicLong(0);private IdGenerator() {}private static class SingletonHolder{private static final IdGenerator instance new IdGenerator();}public static IdGenerator getInstance() {return SingletonHolder.instance;}public long getId() {return id.incrementAndGet();}
} SingletonHolder 是一个静态内部类当外部类IdGenerator被加载的时候并不会创建SingletonHolder实例对象。只有当调用getInstance()方法时SingletonHolder才会被加载这个时候才会创建instance。insance的唯一性、创建过程的线程安全性都由JVM来保证。所以这种实现方法既保证了线程安全又能做到延迟加载。 2.5-枚举 基于枚举类型的单例实现。这种实现方式通过Java枚举类型本身的特性保证了实例创建的线程安全性和实例的唯一性。
public enum IdGenerator {INSTANCE;private AtomicLong id new AtomicLong(0);public long getId() { return id.incrementAndGet();}
} 3-单例存在的问题 大部分情况下我们在项目中使用单例都是用它来表示一些全局唯一类比如配置信息类、连接池类、ID生成器类。单例模式书写简洁、使用方便在代码中我们不需要创建对象直接通过类似IdGenerator.getInstance().getId()这样的方法来调用就可以了。但是这种使用方法有点类似硬编码hard code会带来诸多问题。
3.1-单例对OOP特性的支持不友好 OOP的四大特性是封装、抽象、继承、多态。单例这种设计模式对于其中的抽象、继承、多态都支持得不好。 比如订单业务我们生成订单id 代码 long id IdGenerator.getInstance().getId();用户业务我们生成用户id long id IdGenerator.getInstance().getId(); IdGenerator的使用方式违背了基于接口而非实现的设计原则也就违背了广义上理解的OOP的抽象特性。如果未来某一天我们希望针对不同的业务采用不同的ID生成算法。比如订单ID和用户ID采用不同的ID生成器来生成。为了应对这个需求变化我们需要修改所有用到IdGenerator类的地方这样代码的改动就会比较大。
3.2-单例会隐藏类之间的依赖关系 单例类不需要显示创建、不需要依赖参数传递在函数中直接调用就可以了。如果代码比较复杂这种调用关系就会非常隐蔽。在阅读代码的时候我们就需要仔细查看每个函数的代码实现才能知道这个类到底依赖了哪些单例类。
3.3-单例对代码的扩展性不友好 单例类只能有一个对象实例。如果未来某一天我们需要在代码中创建两个实例或多个实例那就要对代码有比较大的改动。
3.4-单例对代码的可测试性不友好 单例模式的使用会影响到代码的可测试性。如果单例类依赖比较重的外部资源比如DB我们在写单元测试的时候希望能通过mock的方式将它替换掉。而单例类这种硬编码式的使用方式导致无法实现mock替换。
3.5-单例不支持有参数的构造函数 单例不支持有参数的构造函数比如我们创建一个连接池的单例对象我们没法通过参数来指定连接池的大小。怎么解决呢
public class Config {public static final int PARAM_A 123;public static final int PARAM_B 245;
}public class Singleton {private static Singleton instance null;private final int paramA;private final int paramB;private Singleton() {this.paramA Config.PARAM_A;this.paramB Config.PARAM_B;}public synchronized static Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;}
} 4-单例有什么替代解决方案 如果要完全解决这些问题我们可能要从根上寻找其他方式来实现全局唯一类了。比如通过工厂模式、IOC容器比如Spring IOC容器来保证由过程序员自己来保证自己在编写代码的时候自己保证不要创建两个类对象。
5-小结 有人把单例当作反模式主张杜绝在项目中使用。我个人觉得这有点极端。模式没有对错关键看你怎么用。如果单例类并没有后续扩展的需求并且不依赖外部系统那设计成单例类就没有太大问题。对于一些全局的类我们在其他地方new的话还要在类之间传来传去不如直接做成单例类使用起来简洁方便。