单例模式
概念
单例模式指在内存中创建对象且仅创建一次的设计模式。在程序中多次使用同一对象且作用相同时,为了防止频繁地创建对象而是内存飙升,单例模式可以让程序仅在内存中创建一个对象,所有需要调用它的地方都共享这同一个对象。
单例模式的类型
懒汉式:在真正需要使用对象时,才去创建该单例对象。
懒汉式使用对象的方法是:在程序使用对象前先判断对象是否已经实例化,若已实例化则直接返回该类对象,否则先执行实例化对象操作。根据这个流程,可以写出一个懒汉式单例模式:
public class SingleDemo
{
private static SingleDemo singleDemo;//静态属性
private SingleDemo(){}//默认构造函数
public static SingleDemo getInstance()//静态方法
{
if(singleDemo==null)
{
singleDemo=new SingleDemo();
}
return singleDemo;
}
}
注意:这个例子是存在线程安全问题的,也就是当两个线程同时使用对象并且同时判空,那么这两个线程就会分别创建一个 SingleDemo 对象,这就不是单例了。最容易想到的方法就是加锁,可以在方法上加锁或者对类对象加锁:
public class SingleDemo
{
private static SingleDemo singleDemo;
private SingleDemo(){}
//1.在方法上枷锁
public static synchronized SingleDemo getInstance()
{
if(singleDemo==null)
{
singleDemo=new SingleDemo();
}
return singleDemo;
}
}
public class SingleDemo
{
private static SingleDemo singleDemo;
private SingleDemo(){}
public static SingleDemo getInstance()
{//2.对类对象加锁
synchronized(SingleDemo.class)
{
if(singleDemo==null)
{
singleDemo=new SingleDemo();
}
}
return singleDemo;
}
}
注意:规避双例情况的同时,会出现另一种问题,就是每次去获取对象都要先获取锁,并发性能较差,可能会出现卡顿。
改进:优化性能,如果未实例化对象过,就加锁,已经实例化过则不再加锁,直接获取实例。双重校验 + 加锁:
public class SingleDemo
{
private static SingleDemo singleDemo;
private SingleDemo(){}
public static SingleDemo getInstance()
{
if(singleDemo==null)//先判空,再加锁
{
synchronized(SingleDemo.class)
{
if(singleDemo==null)//只有一个进程会进入该分支
{
singleDemo=new SingleDemo();
}
}
}
return singleDemo;
}
}
饿汉式:在类加载时,就已经创建好该单例对象,等待被程序使用,不需要等到被调用时再去创建。写一个饿汉式单例模式:
public class SingleDemo
{
private static final SingleDemo singleDemo=new SingleDemo();
private SingleDemo(){}
public static SingleDemo getInstance()
{
return singleDemo;
}
}
注意:饿汉式不存在并发安全与性能问题。
破坏单例模式
反射和序列化都可以破坏单例对象,产生多个对象。
-
利用反射,强制访问类的私有构造器,创建一个新的对象。
public static void main(String[] args) { Constructor<SingleDemo> construct=SingleDemo.class.getDeclaredConstructor(); construct.setAccessible(true); SingleDemo obj=construct.newInstance(); System.out.println(obj==SingleDemo.getInstance()); //false }
-
利用序列化与反序列化破坏单例模式。
public static void main(String[] args) { ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("SingleDemo.file")); oos.writeObject(SingleDemo.getInstance()); File file=new File("SingleDemo.file"); ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file)); SingleDemo newInstance=(SingleDemo)ois.readObject(); System.out.println(newInstance==SingleDemo.getInstance()); //false }
枚举实现单例模式
在 JDK 1.5 后,使用 Java 语言实现单例模式又多了一种方式:枚举
public enum SingleDemo
{
INSTANCE;
public void doSomething()
{
System.out.println();
}
}
使用枚举的优势:代码简洁,没有做任何额外的处理,线程安全,可以防止反射、序列化与反序列化的破坏。
原因:枚举类默认继承 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是则抛出异常。
工厂模式
概述
Java 有三种工厂模式,提供了一种创建对象的最佳方式,这种类型的设计模式属于创建型模式,在创建对象时不会对客户端暴露创建逻辑,而是通过一个共同的接口来指向新创建的对象。
关键是在一个接口或抽象类中定义一个抽象方法,并让实现该接口的类或抽象类的子类重写这个方法,返回某个子类的实例。
简单工厂模式
提供一个创建对象实例的功能,而无需关心具体实现,被创建的类型可以是接口、抽象类,也可以是具体的类
例子
-
创建一个接口
public interface Car{ String getName(); }
-
创建实现接口的实体类
public class Benz implements Car{ @Override public String getName(){ return "Benz"; } }
public class BMW implements Car{ @Override public String getName(){ return "BMW"; } }
-
创建普通类,简单工厂类,既能生产 Benz 又能生产 BMW
public class SimpleFactory{ public car getCar(String name){ if(name.equals("Benz")) return new Benz(); else if(name.equals("BMW")) return new BMW(); else return null; } }
-
测试
public class SimpleFactoryTest{ public static void main(String[] args){ SimpleFactory simpleFactory=new SimpleFactory(); Car car=simpleFactory.getCar("Benz"); System.out.pringtln(car.getName()); } }
-
测试结果:Benz
总结:用户只要产品而不在乎产品生产过程,但是我们要在简单工厂里嵌套太多 if else 语句,并且繁杂的实际生产操作都要体现在工厂类中,不方便管理。
改进:每个品牌都应该有自己的生产类。
工厂方法
我们希望每个品牌都有自己的生产类,不同品牌的汽车由不同的工厂生产。
例子:
-
创建一个工厂接口,功能就是生产汽车
public interface Factory{ Car getCar(); }
-
Benz 工厂
public class BenzFactory implements Factory{ @Override public Car getCar(){ return new Benz(); } }
-
BMW 工厂
public class BMWFactory implements Factory{ @Override public Car getCar(){ return new BMW(); } }
-
测试
public class FactoryTest{ public static void main(String[] args){ Factory bmwFactory=new BMWFactory(); System.out.println(bmwFactory.getCar().getName()); Factory benzFactory=new BenzFactory(); System.out.println(benzFactory.getCar().getName()); } }
-
测试结果:BMW
改进:在测试类中,当用户想要不同品牌的车时,就要去对应的工厂生产,增加了用户的操作复杂性。
抽象工厂
简化测试类中的用户操作。
-
创建一个抽象类,抽象工厂类
public abstract class AbstractFactory{ protected abstract Car getCar(); public Car getCar(String name){ if("Benz".equalsIgnoreCase(name)) return new BenzFactory().getCar(); else if("BMW".equalsIgnoreCase(name)) return new BMWFactory().getCar(); else return null; } }
-
创建一个默认工厂
public class DefaultFactory extends AbstractFactory{ private DefaultFactory defaultFactory=new DefaultFactory(); public Car getCar(){ return defaultFactory.getCar(); } }
-
测试类
public class AbstractFactoryTest{ public static void main(String[] args){ DefaultFactory factory=new DefaultFactory(); System.out.println(factory.getCar("Benz".getName())); } }
-
测试结果:Benz
总结:用户需要车,只要去找默认工厂提出自己的需求(传参),便能得到想要的产品,而不用根据产品去寻找不同的生产工厂。
代理模式 Proxy
概述
通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
静态代理
代理类对象在程序运行时由开发人员手动创建,代理的目标类是固定的。优点是实现简单、易于理解,缺点是代理类依赖目标类,当目标类增加时,代理类可能需要成倍的增加,不易维护。
实现步骤:
- 创建接口,定义方法
- 创建目标类,实现接口
- 创建代理类,实现接口并引入目标类对象
- 创建代理对象并执行业务方法
动态代理
代理类对象在程序运行时由 JVM 虚拟机动态创建,代理的目标类是可活动、可设置的。优点是代理类不依赖目标类,当目标类增加时,代理类不用成倍增加,代码简单,易于维护。缺点:实现复杂,难于理解。
实现步骤:
- 创建接口,定义目标类要完成的功能
- 创建目标类实现接口
- 创建 InvocationHandler 接口实现类,在 invoke() 方法中完成代理类的功能
- 创建代理对象并把返回值转为接口类型
- 通过代理对象执行业务方法