单例模式
单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例,优点:不会频繁地创建和销毁对象,浪费系统资源。缺点是没有抽象层,难以扩展。
单例模式的常见写法:
-
饿汉式单例模式的写法:线程安全 ,顾名思义,类⼀加载就创建对象,这种⽅式⽐较常⽤,但容易产⽣垃圾对象,浪费内存空间。 优点:线程安全,没有加锁,执⾏效率较⾼缺点:不是懒加载(使⽤的时候再创建对象),类加载时就初始化,浪费内存空间。例如:游戏地图还没打开,地图实例已经加载导致内存拉满,手机卡顿。
public class Singleton {// 1、私有化构造⽅法,避免外界直接new对象private Singleton(){}// 2、定义⼀个静态变量指向⾃⼰类型,私有对象避免直接用类获取private final static Singleton instance = new Singleton();// 3、对外提供⼀个公共的⽅法获取实例public static Singleton getInstance() {return instance;} }
-
懒汉式单例模式的写法:非线程安全 ,优点:懒加载;缺点:线程不安全。
-
目前此种方式的单例确实满足了懒加载,但是如果有多个访问者同时去获取对象实例你可以想象成一堆人在抢厕所,就会造成多个同样的实例并存,从而没有达到单例的要求。
public class Singleton {// 1、私有化构造⽅法,不允许外部直接使用new创建private Singleton(){}// 2、定义⼀个静态变量指向⾃⼰类型private static Singleton instance;// 3、对外提供⼀个公共的⽅法获取实例public static Singleton getInstance(){//public后加上synchronized 就线程安全了,缺点效率低 // 判断为 null 的时候再创建对象if (instance == null) { //可能有多个线程同时访问到这行代码!instance = new Singleton();}return instance; }
-
-
双重检查锁(DCL,即 double-checked locking )单例模式的写法:优点:懒加载,线程安全,效率较高;缺点:实现较复杂 。关于内部的第二重空判断的作用,当多个线程⼀起到达锁位置时,进行锁竞争,其中⼀个线程获取锁,如果是第⼀次进⼊则为 null,会进⾏单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。
public class Singleton {// 1、私有化构造⽅法private Singleton() {}// 2、定义⼀个静态变量指向⾃⼰类型private volatile static Singleton instance;// 3、对外提供⼀个公共的⽅法获取实例public static Singleton getInstance() {// 第一重检查是否为 null ,如果存在就不需要同步。if (instance != null) {return instance;}// 使⽤ synchronized 加锁synchronized (Singleton.class) {// 第二重检查是否为 nullif (instance == null) {// new关键字创建对象不是原⼦操作,123步骤instance = new Singleton();}}return instance;} }
双重检查锁中使用 volatile 的两个重要特性:可见性、禁止指令重排序;这是因为 new 关键字创建对象不是原⼦操作,创建⼀个对象会经历下⾯的步骤:
- 在堆内存开辟内存空间
- 调用构造方法,初始化对象
- 引用变量指向堆内存空间
为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序,创建对象的执行顺序可能为 123或者132。当我们在引用变量上面添加 volatile 关键字以后,会通过在创建对象指令的前后添加内存屏障来禁止指令重排序,就可以避免这个问题,而且对volatile 修饰的变量的修改对其他任何线程都是可见的。不加volatile 导致DCL失效问题:某个线程乱序运⾏ 1 3 2 指令的时候 ,引用变量指向堆内存空间,这个对象不为 null,但是没有初始化,其他线程有可能这个时候进⼊了 getInstance 的第⼀个 if(instance == null) 判断不为 nulll ,导致错误使⽤了没有初始化的⾮ null 实例,这样的话就会出现异常。
-
枚举单例,优点:简单,⾼效,线程安全,可以避免通过反射破坏枚举单例 。
public enum Singleton {INSTANCE;public void doSomething(String str) {System.out.println(str);} } Singleton singleton = Singleton.INSTANCE;
工厂模式
简单工厂模式:简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建 。就是用工厂方法代替new操作的一种模式,可以给系统带来更大的可扩展性和尽量少的修改量(降低耦合)。要什么问工厂拿,而不自己new,如果工厂升级了只需要修改工厂。客户端不需要关注创建逻辑,只需提供传入工厂的参数 。缺点是如果要增加新产品,就需要修改工厂类的判断逻辑,违背开闭原则,且产品多的话会使工厂类比较复杂。 例如咖啡机!
-
优点:客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可 ;客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品; 实现了责任分割。
-
缺点:不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑 。产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护
-
Calendar 抽象类的 getInstance 方法,调用createCalendar 方法根据不同的地区参数创建不同的日历对象。
-
Spring 中的 BeanFactory 使用简单工厂模式,根据传⼊⼀个唯⼀的标识来获得 Bean 对象。
工厂方法模式:和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式具体的产品工厂生产具体的产品只需要定义⼀个抽象工厂,其定义了产品的生产接口,但不负责具体的产品,将生产任务交给不同的派生类工厂,这样不用通过指定类型来创建对象了。
抽象工厂模式
简单工厂模式和工厂方法模式不管工厂怎么拆分抽象,都只是针对⼀类产品,如果要生成另⼀种产品,就比较难办了!抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定 ,以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。这个时候就可以使用抽象工厂类了,抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。
代理模式
代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)。
- 静态代理:以租房为例,租客找房东租房,然后中间经过房屋中介。租客类和代理类都需要实现了租房接口,这就是一个静态代理的前提,那就是真实类和代理类要实现同一个接口,在代理类中实现真实类的方法同时可以进行真实类方法的增强处理。
- 动态代理:当接口增加新方法,目标对象和代理对象都需要修改,这是非常麻烦的。所以就引进动态代理,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建⼀个代理类。动态代理更加灵活 ,实现原理:JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了⼀个个实际的 class ⽂件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
静态代理只能够对一种类型进行代理,如果想要对多种类型进行代理的话就需要创建多个代理类,为了弥补了静态代理的不足,从而出现了动态代理,使用反射技术实现,它是由java.lang.reflect
下面的Proxy
和InvocationHandler
进行支持的。
观察者模式
观察者模式又叫做发布-订阅(Publish/Subscribe)模式 。观察者模式主要用于处理对象间的⼀对多的关系,是⼀种对象行为模式。该模式的实际应用场景比较容易确认,当⼀个对象状态发⽣变化时,所有该对象的关注者均能收到状态变化通知,以进行相应的处理。
优点:被观察者和观察者之间是抽象耦合的 ;被观察者无需关心他的观察者 ;支持广播通信;
缺点:观察者只知道被观察对象发生了变化,但不知变化的过程和缘由 ;被观察者也可能是观察者,如果观察者和被观察者之间产生循环依赖,或者消息传递链路形成闭环,会导致无限循环。
示例:支付场景:用户购买⼀件商品,当支付成功之后三方会回调自身,在这个时候系统可能会有很多需要执行的逻辑(如:更新订单状态,发送邮件通知,赠送礼品…),这些逻辑之间并没有强耦合,因此天然适合使用观察者模式去实现这些功能,
模板方法模式
模板方法模式是指定义一个算法骨架,将具体内容延迟到子类去实现。 Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使⽤到了模板模式。 例如:喝茶分为三步:烧水、放入茶叶泡茶、品茶;那么烧水和品茶相同代码可以父类实现,而具体泡什么茶交给子类实现。
优点:提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中; 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制并且符合开闭原则。
适配器模式
在我们的应用程序中我们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将⼀个类的接口,转换成客户期望的另⼀个接口:有点像欧标充电器适配器。它可以让原本两个不兼容的接口能够无缝完成对接。通过类继承实现适配,继承 Target 的接口,继承 Adaptee 的实现。
Target:定义 Client 真正需要使⽤的接口;Adaptee: 其中定义了⼀个已经存在的接口,也是我们需要进行适配的接口。Adapter: 对 Adaptee 和 Target 的接口进⾏适配,保证对 target 中接口的调⽤可以间接转换为对 Adaptee 中接口进行调用。
优点:组合若⼲关联对象形成对外提供统⼀服务的接口;提高了类的复用(不用再造新类);
缺点:过多使⽤适配模式容易造成代码功能和逻辑意义的混淆。