2 设计模式--创建型模式【五种】
- 创建型模式的关注点是 "怎/样创建出对象"
- 将对象的创建 与 使用分离
- 降低系统耦合度
- 使用者无需关注对象的创建细节
- 对象的创建由相关的工厂完成 (各种工厂模式)
- 对象的创建由一个建造者来完成(建造者模式)
- 对象的创建由原来对象克隆完成(原型模式)
- 对象是在在系统中只有一个实例(单例模式)
2.1 单例模式
Ensure a class has only one instance, and provide a global point of access to it.
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例特点:
- 某个类只能有一个实例(构造器私有)
- 它必须自行创建这个实例(自己编写实例化逻辑)
- 它必须自行向整个系统提供这个实例(对外提供实例化方法)
| 优点 |
|---|
| 1.由于在内存中只有一个实例,减少了内存开支,特别是一个对象要频繁创建、销毁时,且创建或销毁时性能又无法优化,单例模式的优势就非常明显; |
| 2.当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决;(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制); |
| 3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作; |
| 4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理; |
| 缺点 |
|---|
| 1.单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。 |
| 2.单例模式为什么不能增加接口呢? 因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。 |
| 3.单例模式对测试是不利的:在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。 |
| 4.单例模式与单一职责原则有冲突:一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。 |
适用场景
1.要求生成唯一序列号的环境;
2.在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
3.创建一个对象需要消耗的资源过多, 如要访问IO和数据库等资源;
4.需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static的方式)
J2EE标准中ServletContext、ServletContextConfig等,
Spring框架应用中的ApplicationContext、数据库连接池等也都是单例形式。
懒汉式:在真正需要使用时才去创建该对象;被外部类调用的时候,内部类才会加载;
在开发中对内存要求非常高时,使用懒汉式
饿汉式:在类加载时已经创建好该对象,等待被程序使用;
对内存要求不高时,使用饿汉式,简单不易错,且没有并发安全和性能问题;
懒汉模式1(线程不安全):#并发的情况下,若两个线程同时判断,会变成双例;
懒汉模式2(在方法上加锁):#虽然规避了线程安全问题,但每次获取对象前都要先获取锁,并发性能差
懒汉模式3(双重检查锁):#但还存在:指令重排序问题
懒汉模式4(指令重排):#其指令执行顺序可能会发生变化,在声明对象时,加 volatile 关键字修饰变量;
懒汉模式5(内部类):#内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题
饿汉模式(线程安全):#因为在类加载时已经创建好了对象,只用调用就可以了,所以是线程安全的
#上面的单例模式对于创建来说都是不安全的,因为用反射和序列化破坏掉;
反射破坏单例模式:#
枚举式单例模式:#使用枚举可以规避反射破坏单例
2.1.1 懒汉模式1(线程不安全)
class LazySingletonV1 {
private static LazySingletonV1 lazySingletonV1;//声明对象
private LazySingletonV1() {//限制产生多个对象
System.out.println(Thread.currentThread().getName());//测试打印线程名称
}
/** 一
* 创建对象方法时在程序使用对象前,先判断该对象是否实例化,若实例化直接返回该对象,否则先执行实例化操作;
* 但是,在并发的情况下,若两个线程同时判断,则会变成双例,则需要解决线程安全问题
*/
public static LazySingletonV1 getLazySingletonV1() {
if (lazySingletonV1 == null) {
lazySingletonV1 = new LazySingletonV1();
}
return lazySingletonV1;
}
//类中其他方法,尽量是static的
public static void doSomething() {}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(LazySingletonV1::getLazySingletonV1).start();
}
}
}
2.1.2 懒汉模式2(在方法上加锁)
class LazySingletonV2 {
private static LazySingletonV2 lazySingletonV2;//声明对象
private LazySingletonV2() {}//限制产生多个对象
/** 二
* 解决线程安全问题:在方法上加锁,或在对象上加锁
* 虽然规避了线程安全问题,但又:每次获取对象前都要先获取锁,并发性能差
*/
public static synchronized LazySingletonV2 getLazySingletonV2a() {
if (lazySingletonV2 == null) {
lazySingletonV2 = new LazySingletonV2();
}
return lazySingletonV2;
}
/** 或者 **/
public static LazySingletonV2 getLazySingletonV2b() {
synchronized (LazySingletonV2.class) {
if (lazySingletonV2 == null) {
lazySingletonV2 = new LazySingletonV2();
}
}
return lazySingletonV2;
}
//类中其他方法,尽量是static的
public static void doSomething() {}
}
2.1.3 懒汉模式3(双重检查锁)
class LazySingletonV3 {
private static LazySingletonV3 lazySingletonV3;//声明对象
private LazySingletonV3() {}//限制产生多个对象
/** 三
* 解决并发安全 + 性能低效,也称为:Double Check + Lock [双检查锁]
* 但还存在:指令重排序问题
*/
public static LazySingletonV3 getLazySingletonV3() {
if (lazySingletonV3 == null) { //只有在第一次未实例化时,线程才会抢锁
synchronized (LazySingletonV3.class) {
if (lazySingletonV3 == null) {
lazySingletonV3 = new LazySingletonV3();
}
}
}
return lazySingletonV3;
}
//类中其他方法,尽量是static的
public static void doSomething() {}
}
2.1.4 懒汉模式4(指令重排)
class LazySingletonV4 {
/**
* 在上面的基础上
* 指令重排问题:在声明对象时,加 volatile 关键字修饰变量
* 可保证其指令执行顺序不会发生变化,在多线程环境下就不会发生NPE异常
*/
//声明对象
private static volatile LazySingletonV4 lazySingletonV4;
//限制产生多个对象
private LazySingletonV4() {}
public static LazySingletonV4 getLazySingletonV4() {
if (lazySingletonV4 == null) { //只有在第一次未实例化时,线程才会抢锁
synchronized (LazySingletonV4.class) {
if (lazySingletonV4 == null) {
lazySingletonV4 = new LazySingletonV4();
}
}
}
return lazySingletonV4;
}
//类中其他方法,尽量是static的
public static void doSomething() {}
}
2.1.5 懒汉模式5(内部类)
class LazySingletonV5 {
/**
* 这种模式兼顾饿汉式单例模式的内存浪费问题 和 synchronized性能问题
* 内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。
*/
//在使用LazySingletonV5的时候,会默认先初始化内部类
//如果没有使用,内部类则不加载
private LazySingletonV5() {
System.out.println(Thread.currentThread().getName());//测试打印线程名称
}
//每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不被重写、重载
public static LazySingletonV5 getLazySingletonV5() {
return LazyHolder.LAZY;//在返回结果前,一定会先加载内部类
}
//默认不加载
private static class LazyHolder {
private static final LazySingletonV5 LAZY = new LazySingletonV5();
}
//类中其他方法,尽量是static的
public static void doSomething() {}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(LazySingletonV5::getLazySingletonV5).start();
}
}
}
2.1.6 饿汉模式(线程安全)
class HungrySingleton {
//在类加载时创建该对象
private static final HungrySingleton hungrySingleton = new HungrySingleton();
//限制产生多个对象
private HungrySingleton() {}
//通过该方法获取实例对象
public static HungrySingleton getHungrySingleton() {
return hungrySingleton;
}
//类中其他方法,尽量是static的
public static void doSomething() {}
}
2.1.7 反射破坏单例模式
上面介绍的单例模式的构造方法除了加上private关键字,没有做任何的处理。如果我们使用反射来调用其构造方法,再调用getInstance()方法,应该有两个不同的实例。
//反射破坏单例模式
class ReflectionBreakSingleton {
public static void main(String[] args) {
try {
//先正常获取单例对象
LazySingletonV4 instance = LazySingletonV4.getLazySingletonV4();
//进行破坏,获得空参构造器
Constructor<LazySingletonV4> constructor = LazySingletonV4.class.getDeclaredConstructor();
//强制访问
constructor.setAccessible(true);
//暴力初始化
LazySingletonV4 instance2 = constructor.newInstance();
System.out.println(instance == instance2);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
运行结果如下:
false
显然,创建了两个不同的实例,我们可以在其构造方法中做一些限制,避免重复创建
2.1.9 枚举式单例模式
public enum EnumSingleton {
INSTANCE;
//在枚举类中申明一个新的成员变量,才能实现扩展
private Object data;
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举类型其实通过类名和类对象找到唯一一个枚举对象。因此,枚举对象不可能被类加载器加载多次。
那么反射是否能破坏枚举式单例模式呢?
class ReflectionBreakEnumSingleton{
public static void main(String[] args) {
try {
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
java.lang.NoSuchMethodException: com.banksy.createModel.singleton.Enumsingleton.<init>()
结果中报的是java.lang.NoSuchMethodException异常,意思是没找到无参的构造方法。这时候,我们打开java.lang.Enum的源码,查看他的构造方法,只有一个protected类型的构造方法,代码如下:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
class ReflectionBreakEnumSingleton{
public static void main(String[] args) {
try {
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
//这时的错误已经非常明显了,“Cannot reflectively create enum objects”,即不能用反射来创建枚举类型
进入Constructor的newInstance()方法源码可发现,在newInstance()方法调用的newInstanceWithCaller方法中调用的acquireConstructorAccessor做出了强制的判断,如果修饰符是Modifier.ENUM 枚举类型,直接抛出异常。
到此为止,我们已经非常清晰明了了吗?JDK枚举的语法特殊性及反射也为枚举保驾护航,让枚举单例模式成为一种比较优雅的实现。
···
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
···
2.2 原型模式
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象。
优点
性能优良【原型模式是在内存二进制流的拷贝, 要比直接new一个对象性能好很多】;
逃避构造函数的约束【直接在内存中拷贝,构造函数是不会执行,减少了约束】;
缺点
逃避构造函数的约束【直接在内存中拷贝,构造函数是不会执行,减少了约束】;
适用场景
在并发场景下,线程1在使用对象1,在还未使用结束的情况下,线程2也需要使用对象1,就会更新对象1中的属性,此时就会出现线程安全问题;【解决方法】利用原型模式进行拷贝一个相同的对象进行使用;
扩展
可对 原型模式 + 工厂模式 => 只new一次,剩下都拷贝就行了;
public class Prototype {
public static void main(String[] args) {
Date date = new Date();
/**
* 浅拷贝:不仅把值拷贝过来,把引用也拷贝过来
**/
System.out.println("=========================浅拷贝=======================");
PrototypeShallowCopy shallowCopy = new PrototypeShallowCopy("浅拷贝......", date);
PrototypeShallowCopy shallowCopy1 = shallowCopy.clone();
System.out.println(shallowCopy.toString());
System.out.println(shallowCopy1.toString());
System.out.println("=========================修改date后=======================");
date.setTime(8686767);
System.out.println(shallowCopy.toString());
System.out.println(shallowCopy1.toString());//克隆对象的date属性也变化了,说明两个对象同时指向同一个date
System.out.println();
System.out.println();
/**
* 深拷贝:把值拷贝,未拷贝引用
**/
Date date1 = new Date();
System.out.println("=========================深拷贝=======================");
PrototypeDeepCope deepCope = new PrototypeDeepCope("深拷贝......", date1);
PrototypeDeepCope deepCope1 = deepCope.clone();
System.out.println(deepCope.toString());
System.out.println(deepCope1.toString());
System.out.println("=========================修改date后=======================");
date1.setTime(32525);
System.out.println(deepCope.toString());//此时原始对象的Date属性发生改变
System.out.println(deepCope1.toString());//克隆对象的Date未发生改变,说明两个对象指向不同的Date
}
}
/**
* 浅拷贝
* @author banksy
**/
public class PrototypeShallowCopy implements Cloneable {
private String name;
private Date createTime;
@Override
protected PrototypeShallowCopy clone() {
PrototypeShallowCopy prototypeShallowCopy = null;
try {
prototypeShallowCopy = (PrototypeShallowCopy) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototypeShallowCopy;
}
······构造函数、getset、toString
}
/**
* 深拷贝
* @author banksy
**/
public class PrototypeDeepCope implements Cloneable {
private String name;
private Date createTime;
@Override
protected PrototypeDeepCope clone() {
PrototypeDeepCope prototypeDeepCope = null;
try {
prototypeDeepCope = (PrototypeDeepCope) super.clone();
prototypeDeepCope.createTime = (Date) this.createTime.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototypeDeepCope;
}
······构造函数、getset、toString
}
2.3 工厂模式
2.3.1 简单工厂模式
简单工厂,又称静态工厂方法模式;根据参数但不同返回不同类的实例;
简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都是具有共同的父类;
简单工厂模式是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于GoF的23种设计模式;
简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心;
简单工厂模式也有缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构,横向扩展时,需要修改已有的代码;
//测试
class SimpleFactoryClient {
public static void main(String[] args) {
SimpleProduct simpleProduct = SimpleFactory.createProduct("A");
simpleProduct.print();
}
}
//工厂类
public class SimpleFactory {
static SimpleProduct createProduct(String type) {
if (type.equals("A"))
return new SimpleProduct1();
else
return new SimpleProduct2();
}
}
//抽象类
abstract class SimpleProduct {
public abstract void print();
}
//实体类,产品1
class SimpleProduct1 extends SimpleProduct {
@Override
public void print() {
System.out.println("产品1");
}
}
//实体类,产品2
class SimpleProduct2 extends SimpleProduct {
@Override
public void print() {
System.out.println("产品2");
}
}
2.3.2 工厂方法模式
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.
定义一个用于创建对象的接口, 让子类决定实例化哪一个类。 工厂方法使一个类的实例化延迟到其子类。
优点:良好的封装性, 代码结构清晰。
工厂方法模式的扩展性非常优秀。
屏蔽产品类。
工厂方法模式是典型的解耦框架。符合迪米特法则,依赖倒置原则,里氏替换原则
缺点:
1、类的个数容易过多,增加复杂度
2、增加了系统的抽象性和理解难度
适用场景:
1、创建对象需要大量重复的代码。
2、客户端(应用层)不依赖于产品类实例如何被创建、如何被实现等细节
3、一个类通过其子类来指定创建哪个对象
横向扩展,不需要修改已有代码,只需添加工厂实体类和产品实体类即可
class MethodFactoryClient {
public static void main(String[] args) {
MethodFactoryA methodFactoryA = new MethodFactoryA();//去指定工厂,要产品
methodFactoryA.getProduct().print();
}
}
//抽象工厂类
abstract class MethodFactory {
public abstract MethodProduct getProduct();
}
//实体类,工厂A
class MethodFactoryA extends MethodFactory {
@Override
public MethodProduct getProduct() {
return new MethodProduct1();
}
}
//实体类,工厂B
class MethodFactoryB extends MethodFactory {
@Override
public MethodProduct getProduct() {
return new MethodProduct2();
}
}
//抽象产品类
abstract class MethodProduct {
public abstract void print();
}
//实体类,产品A
class MethodProduct1 extends MethodProduct {
@Override
public void print() {
System.out.println("产品1");
}
}
//实体类,产品B
class MethodProduct2 extends MethodProduct {
@Override
public void print() {
System.out.println("产品2");
}
}
2.3.3 抽象工厂模式
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
为创建一组相关或相互依赖的对象提供一个接口, 而且无须指定它们的具体类。
优点: 封装性,【每个产品的实现类不是高层模块要关心的,它关心的是接口,是抽象;
它不关心对象是如何创建出来,关心由谁负责的,工厂类, 知道工厂类是谁,就能创建出需要的对象】
产品族内的约束为非公开状态
缺点:
横向扩展容易, 纵向扩展困难。
适用场景:
一个对象族都有相同的约束, 则可以使用抽象工厂模式。
class AbstractFactoryClient {
public static void main(String[] args) {
AbstractFactoryA abstractFactoryA = new AbstractFactoryA();
abstractFactoryA.abstractProduct1().print();//A工厂能造产品1,没有指定具体产品类
abstractFactoryA.abstractProduct2().print();//A工厂能造产品2
}
}
//任何工厂都能造,这两个产品
//抽象工厂,超级工厂
abstract class AbstractFactory {
public abstract AbstractProduct1 abstractProduct1();//生产产品1
public abstract AbstractProduct2 abstractProduct2();//生产产品2
}
//抽象类,产品1
abstract class AbstractProduct1 {
public abstract void print();
}
//抽象类,产品2
abstract class AbstractProduct2 {
public abstract void print();
}
//实体类,工厂A
class AbstractFactoryA extends AbstractFactory {
@Override
public AbstractProduct1 abstractProduct1() {
return new AbstractProductA1();
}
@Override
public AbstractProduct2 abstractProduct2() {
return new AbstractProductA2();
}
}
//实体类,工厂B
class AbstractFactoryB extends AbstractFactory {
@Override
public AbstractProduct1 abstractProduct1() {
return new AbstractProductB1();
}
@Override
public AbstractProduct2 abstractProduct2() {
return new AbstractProductB2();
}
}
//实体类,工厂A产品1
class AbstractProductA1 extends AbstractProduct1 {
@Override
public void print() {
System.out.println("A工厂产品1");
}
}
//实体类,工厂B产品1
class AbstractProductB1 extends AbstractProduct1 {
@Override
public void print() {
System.out.println("B工厂产品1");
}
}
//实体类,工厂A产品2
class AbstractProductA2 extends AbstractProduct2 {
@Override
public void print() {
System.out.println("A工厂产品2");
}
}
//实体类,工厂B产品2
class AbstractProductB2 extends AbstractProduct2 {
@Override
public void print() {
System.out.println("B工厂产品2");
}
}
2.4 建造者模式
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示。
优点:封装性【不必须知道每一个具体的模型内部是如何实现的】;
建造者独立、容易扩展;
便于控制细节风险【具体的建造者是独立的, 因此可以对建造过程逐步细化,而不会对其他的模块产生任何影响】;
适用场景:
相同方法,不同执行顺序,产生不同事件结果;
多个部件或零件, 都可以装配到一个对象中, 但是产生的运行结果又不相同;
产品类非常复杂, 或者产品类中的调用顺序不同产生了不同的效能;
注意:建造者模式关注的是顺序,这是和工厂模式最大的不同
public class BuilderClient {
public static void main(String[] args) {
Director director = new Director();
Product product = director.getProduct();
product.doSomething();
}
}
//导演类:起到封装的作用,避免高层模块深入到建造者内部的实现类
class Director {
private AbstractBuilder concreteBuilder = new ConcreteBuilder();
public Product getProduct() {
concreteBuilder.setPart();
return concreteBuilder.buildProduct();
}
}
//抽象建造者
abstract class AbstractBuilder {
//设置产品的不同部分,以获得不同的产品
public abstract void setPart();
//建造产品
public abstract Product buildProduct();
}
//具体建造者
class ConcreteBuilder extends AbstractBuilder {
private Product product = new Product();
@Override
public void setPart() {
}
@Override
public Product buildProduct() {
return product;
}
}
//产品类
class Product {
public void doSomething() {
System.out.println("产品类......");
}
}